¿Cómo obtengo la hora UTC de “medianoche” para una zona horaria determinada?

Lo mejor que se me ocurre por ahora es esta monstruosidad:

>>> 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, obtenga la hora actual (en UTC), conviértala a otra zona horaria, establezca la hora a la medianoche y luego vuelva a convertirla a UTC.

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

No puedo dejar de sentir que me estoy perdiendo algo, ¿alguna idea?

Creo que puedes eliminar algunas llamadas de método si lo haces así:

 >>> 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: dará un resultado incorrecto el día del cambio hacia o desde el horario de verano.

El motivo de esto es que ni los constructores de datetime ni replace() toman 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() establece:

Este método se debe utilizar para construir tiempos locales, en lugar de pasar un argumento tzinfo a un constructor de fecha y hora.

Así, tu problema se resuelve así:

 >>> 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 

Sin embargo, no hay garantías para las fechas anteriores a 1582.

La respuesta de @ hop es incorrecta el día de la transición desde el horario de verano (DST), por ejemplo, el 1 de abril de 2012. Para solucionarlo, podría usarse tz.localize() :

 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 los 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) 

La configuración de la variable de entorno TZ modifica con qué zona horaria funcionan las funciones de fecha y hora 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) 

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

  time.timezone 

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

time.timezone

El desplazamiento de la zona horaria local (no DST), en segundos al oeste de UTC (negativo en la mayor parte de Europa occidental, positivo en los EE. UU., Cero en el Reino Unido).

Entonces, simplemente usaría ese número para el tiempo en horas si es positivo (es decir, si es medianoche en Chicago (que tiene un valor de zona horaria +6), entonces es 6000 = 6am UTC).

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