Conversión de zona horaria de fecha y hora usando pytz

Este es solo otro post en pytz .

Hay dos funciones para convertir objetos de fecha y hora entre dos zonas horarias. La segunda función funciona para todos los casos. La primera función falla en dos casos, (3) y (4). Una publicación SO similar no tuvo un problema como este. Cualquier explicación basada en la diferencia entre localize(datetime.datetime) y replace(tzinfo) sería de gran ayuda.

 >>> from dateutil.parser import parse >>> import pytz 

Primera función (buggy)

La siguiente función utiliza datetime.datetime.replace(tzinfo) .

 def buggy_timezone_converter(input_dt, current_tz='UTC', target_tz='US/Eastern'): '''input_dt is a datetime.datetime object''' current_tz = pytz.timezone(current_tz) target_tz = pytz.timezone(target_tz) target_dt = input_dt.replace(tzinfo=current_tz).astimezone(target_tz) return target_tz.normalize(target_dt) 

Observe la conversión de cuatro tiempos ahora.

(1) de UTC a EST – OK

 >>> buggy_timezone_converter(parse('2013-02-26T04:00:00')) Out[608]: datetime.datetime(2013, 2, 25, 23, 0, tzinfo=) 

(2) de UTC a EDT – OK

 >>> buggy_timezone_converter(parse('2013-05-26T04:00:00')) Out[609]: datetime.datetime(2013, 5, 26, 0, 0, tzinfo=) 

(3) de EST a UTC – No está bien. El tiempo de desplazamiento es de 4 horas y 56 minutos. Se supone que son 5 horas.

 >>> buggy_timezone_converter(parse('2013-02-26T04:00:00'), target_tz='UTC', current_tz='US/Eastern') Out[610]: datetime.datetime(2013, 2, 26, 8, 56, tzinfo=) 

(4) de EDT a UTC – No está bien. El tiempo de desplazamiento es de 4 horas y 56 minutos. Se supone que es de 4 horas. No se considera el horario de verano.

 >>> buggy_timezone_converter(parse('2013-05-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC') Out[611]: datetime.datetime(2013, 5, 26, 8, 56, tzinfo=) 

Segunda función (Funciona perfectamente)

La siguiente función utiliza pytz.timezone.localize(datetime.datetime) . Funciona perfectamente

 def good_timezone_converter(input_dt, current_tz='UTC', target_tz='US/Eastern'): current_tz = pytz.timezone(current_tz) target_tz = pytz.timezone(target_tz) target_dt = current_tz.localize(input_dt).astimezone(target_tz) return target_tz.normalize(target_dt) 

(1) de UTC a EST – OK

 >>> good_timezone_converter(parse('2013-02-26T04:00:00')) Out[618]: datetime.datetime(2013, 2, 25, 23, 0, tzinfo=) 

(2) de UTC a EDT – OK

 >>> good_timezone_converter(parse('2013-05-26T04:00:00')) Out[619]: datetime.datetime(2013, 5, 26, 0, 0, tzinfo=) 

(3) de EST a UTC – OK.

 >>> good_timezone_converter(parse('2013-02-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC') Out[621]: datetime.datetime(2013, 2, 26, 9, 0, tzinfo=) 

(4) de EDT a UTC – OK.

 >>> good_timezone_converter(parse('2013-05-26T04:00:00'), current_tz='US/Eastern', target_tz='UTC') Out[620]: datetime.datetime(2013, 5, 26, 8, 0, tzinfo=) 

Supongo que tiene estas preguntas:

  • ¿Por qué la primera función funciona para la zona horaria UTC?
  • ¿Por qué falla para 'US/Eastern' zona horaria 'US/Eastern' (instancia de DstTzInfo )?
  • ¿Por qué la segunda función funciona para todos los ejemplos proporcionados?

La primera función es incorrecta porque utiliza d.replace(tzinfo=dsttzinfo_instance) lugar de dsttzinfo_instance.localize(d) .

La segunda función es correcta la mayor parte del tiempo, excepto durante tiempos ambiguos o inexistentes, por ejemplo, durante las transiciones DST. Puede cambiar el comportamiento pasando el parámetro .localize() a .localize() : False (predeterminado) / True / None excepción).

La primera función funciona para la zona horaria UTC porque tiene un desplazamiento de utc fijo (cero) para cualquier fecha. Otras zonas horarias como America/New_York pueden tener diferentes compensaciones de utc en diferentes momentos (horario de verano, tiempo de guerra, cada vez que un político local pueda pensar que es una buena idea, puede ser cualquier cosa) , la base de datos tz funciona en la mayoría de los casos ). Para implementar los tzinfo.utcoffset(dt) , tzinfo.tzname(dt) , tzinfo.dst(dt) pytz utiliza una colección de instancias DstTzInfo , cada una con un conjunto diferente de (_tzname, _utcoffset, _dst) . Dado el método dt (fecha / hora) y is_dst , .localize() elige una instancia DstTzInfo apropiada (en la mayoría de los casos, pero no siempre ) de la colección. pytz.timezone('America/New_York') devuelve una instancia de DstTzInfo con (_tzname, _utcoffset, _dst) atributos que corresponden a algún momento no documentado en el tiempo (diferentes versiones de pytz pueden devolver diferentes valores; la versión actual puede devolver la instancia de tzinfo correspondiente hasta la fecha más temprana para la que está disponible zoneinfo: no desea este valor la mayor parte del tiempo: creo que la motivación detrás de la elección del valor predeterminado es resaltar el error (pasar pytz.timezone al constructor datetime o .replace() método).

Para resumir: .localize() selecciona los valores apropiados de utcoffset, tzname, dst, .replace() usa el valor predeterminado (inapropiado). UTC solo tiene un conjunto de utcoffset, tzname, dst, por lo que se puede usar el valor predeterminado y el método .replace() funciona con la zona horaria UTC. is_dst pasar un objeto de fecha y hora y el parámetro is_dst para seleccionar los valores apropiados para otras zonas horarias como 'America/New_York' .

En principio, pytz podría haber llamado al método localize() para implementar los utcoffset() , tzname() , dst() incluso si dt.tzinfo == self : haría estos métodos O (log n) en el tiempo donde n es número de intervalos con diferentes valores (utcoffset, tzname, dst) pero datetime constructor y .replace() funcionarán como es, es decir, la llamada explícita localize() solo sería necesaria para pasar is_dst .