2008-12-17 31 views
32

lo mejor que pueda llegar a por ahora es esta monstruosidad:¿Cómo obtengo la hora UTC de "medianoche" para una zona horaria determinada?

>>> datetime.utcnow() \ 
... .replace(tzinfo=pytz.UTC) \ 
... .astimezone(pytz.timezone("Australia/Melbourne")) \ 
... .replace(hour=0,minute=0,second=0,microsecond=0) \ 
... .astimezone(pytz.UTC) \ 
... .replace(tzinfo=None) 
datetime.datetime(2008, 12, 16, 13, 0) 

Es decir, en Inglés, obtener la hora actual (en UTC), convertirlo a alguna otra zona horaria, ajustar el tiempo hasta la medianoche, y luego volver a convertir a UTC.

No estoy utilizando solo now() o localtime() ya que usaría la zona horaria del servidor, no la zona horaria del usuario.

No puedo evitar sentir que me falta algo, alguna idea?

Respuesta

46

Creo que se puede afeitarse unas pocas llamadas de método si lo haces de esta manera:

>>> from datetime import datetime 
>>> datetime.now(pytz.timezone("Australia/Melbourne")) \ 
      .replace(hour=0, minute=0, second=0, microsecond=0) \ 
      .astimezone(pytz.utc) 

PERO ... hay un problema más grande que la estética en su código: se dará un resultado erróneo en el día del cambio hacia o desde el horario de verano.

El motivo es que ni los constructores de fecha y hora ni replace() tienen en cuenta los cambios de horario de verano.

Por ejemplo:

>>> now = datetime(2012, 4, 1, 5, 0, 0, 0, tzinfo=pytz.timezone("Australia/Melbourne")) 
>>> print now 
2012-04-01 05:00:00+10:00 
>>> print now.replace(hour=0) 
2012-04-01 00:00:00+10:00 # wrong! midnight was at 2012-04-01 00:00:00+11:00 
>>> print datetime(2012, 3, 1, 0, 0, 0, 0, tzinfo=tz) 
2012-03-01 00:00:00+10:00 # wrong again! 

Sin embargo, la documentación para tz.localize() estados:

Este método debe ser usado para construir localtimes, en lugar de pasar un argumento tzinfo a un constructor de fecha y hora.

Por lo tanto, el problema se resuelve de esta manera:

>>> import pytz 
>>> from datetime import datetime, date, time 

>>> tz = pytz.timezone("Australia/Melbourne") 
>>> the_date = date(2012, 4, 1) # use date.today() here 

>>> midnight_without_tzinfo = datetime.combine(the_date, time()) 
>>> print midnight_without_tzinfo 
2012-04-01 00:00:00 

>>> midnight_with_tzinfo = tz.localize(midnight_without_tzinfo) 
>>> print midnight_with_tzinfo 
2012-04-01 00:00:00+11:00 

>>> print midnight_with_tzinfo.astimezone(pytz.utc) 
2012-03-31 13:00:00+00:00 

No hay garantías para fechas anteriores al 1582, sin embargo.

+2

no se olvide de milisegundos – joeforker

+0

parece que ignora el horario de verano. En alguna parte '.localize() /. Normalize()' podría ser necesario. – jfs

+0

@ J.F.Sebastian: ¡interesante! ¿Estás seguro? ¿Tienes un ejemplo? es completamente posible. – hop

0

La configuración de la variable de entorno TZ modifica las funciones de fecha y hora de la zona horaria de Python.

>>> time.gmtime() 
(2008, 12, 17, 1, 16, 46, 2, 352, 0) 
>>> time.localtime() 
(2008, 12, 16, 20, 16, 47, 1, 351, 0) 
>>> os.environ['TZ']='Australia/Melbourne' 
>>> time.localtime() 
(2008, 12, 17, 12, 16, 53, 2, 352, 1) 
+0

Además de no querer usar la variable TZ para controlar esto, eso en realidad no me dice cómo encontrar la medianoche, solo la hora actual. – Tom

0

Cada zona horaria tiene un número, por ejemplo, US/Central = -6. Esto se define como el desplazamiento en horas desde UTC. Como 0000 es medianoche, puede usar este desplazamiento para encontrar la hora en cualquier zona horaria cuando sea la medianoche UTC. Para acceder a esto, creo que se puede utilizar

 time.timezone

Según The Python Docs, time.timezone en realidad le da el valor negativo de este número:

time.timezone

El desplazamiento de la zona horaria local (que no es DST), en segundos al oeste de UTC (negativa en la mayoría de Europa occidental, positiva en los EE. UU., cero en el Reino Unido).

Así que sólo tendría que usar ese número para el tiempo en horas si es positivo (es decir, si se trata de la medianoche en Chicago (que tiene un valor de 6 zona horaria), entonces es 6000 = 06 a.m. GMT) .

Si el número es negativo, reste de 24. Por ejemplo, Berlín daría -1, entonces 24 - 1 => 2300 = 11 pm.

+0

Creo que estás en el camino correcto, pero ¿cómo sabes qué fecha comenzar? es decir, hace unas horas, era el número 17 aquí en Melbourne, mientras que todavía era el día 16 en UTC. – Tom

+0

La pregunta es sobre la medianoche local. La relación de día se fija mediante el desplazamiento UTC para la zona horaria, en la medianoche local. –

+0

sumar/restar diferencias a mano podría tener problemas con el interruptor desde y hasta DST – hop

22

@hop's answer está mal en el día de transición de horario de verano (DST), por ejemplo, Apr 1, 2012. Para solucionarlo tz.localize() podrían utilizarse:

tz = pytz.timezone("Australia/Melbourne") 
today = datetime.now(tz).date() 
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None) 
utc_dt = midnight.astimezone(pytz.utc)   

Lo mismo con comentarios:

#!/usr/bin/env python 
from datetime import datetime, time 
import pytz # pip instal pytz 

tz = pytz.timezone("Australia/Melbourne") # choose timezone 

# 1. get correct date for the midnight using given timezone. 
today = datetime.now(tz).date() 

# 2. get midnight in the correct timezone (taking into account DST) 
#NOTE: tzinfo=None and tz.localize() 
# assert that there is no dst transition at midnight (`is_dst=None`) 
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None) 

# 3. convert to UTC (no need to call `utc.normalize()` due to UTC has no 
# DST transitions) 
fmt = '%Y-%m-%d %H:%M:%S %Z%z' 
print midnight.astimezone(pytz.utc).strftime(fmt) 
+0

Estoy un poco confundido. El interruptor de DST se realizó a las 3 a.m., por lo que la medianoche de ese día todavía debería ser a las 14:00 UTC, no a las 13:00. ¿no? – hop

+0

@hop: convierte 2012 Mar 31 13:00 UTC a la zona horaria de Melbourne y compruébalo tú mismo (sigue siendo +11 zona horaria (DST), no +10 (estándar)) – jfs

Cuestiones relacionadas