Python: dada la hora actual en UTC, ¿cómo determina la hora de inicio y finalización del día en una zona horaria en particular?

Estoy jugando con Google App Engine y aprendí que la zona horaria está fijada en UTC. Quiero determinar la hora de inicio y finalización del día actual para la zona horaria local del usuario. Básicamente, dada la hora actual en UTC, ¿cómo determina la hora de inicio y finalización del día actual, teniendo en cuenta los cambios de horario de verano?

Tengo un código de ejemplo torpe. Tenga en cuenta que me doy cuenta de que si estoy especificando una fecha manualmente, también podría especificar la fecha de mañana, pero son ejemplos y quiero determinarla de manera programática. Mi principal problema es que si agrego un timedelta a un datetime con un huso horario y luego lo normalizo (como se sugiere en los documentos de pytz) obtengo un datetime que es una hora libre durante los cambios de horario de verano.

No se menciona en el código, pero en última instancia, el objective es convertir estos tiempos de nuevo a UTC, por lo que es importante tener en cuenta la zona horaria.

#!/usr/bin/python import datetime from pytz.gae import pytz hobart_tz = pytz.timezone('Australia/Hobart') utc_dt = pytz.utc.localize(datetime.datetime.utcnow()) hobart_dt = utc_dt.astimezone(hobart_tz) # create a new datetime for the start of the day and add a day to it to get tomorrow. today_start = datetime.datetime(hobart_dt.year, hobart_dt.month, hobart_dt.day) today_start = hobart_tz.localize(today_start) today_end = hobart_tz.normalize(today_start + datetime.timedelta(days=1)) print 'today:', today_start print ' next:', today_end print # gives: # today: 2011-08-28 00:00:00+10:00 # next: 2011-08-29 00:00:00+10:00 # but say today is a daylight savings changeover. # after normalisation, we are off by an hour. dst_finish_2011 = datetime.datetime(2011, 4, 3) # this would come from hobart_dt dst_finish_2011 = hobart_tz.localize(dst_finish_2011) next = hobart_tz.normalize(dst_finish_2011 + datetime.timedelta(days=1)) print '2011-04-03:', dst_finish_2011 print '2011-04-04:', next # expect 2011-04-04 00:00:00+10:00 print # gives # 2011-04-03: 2011-04-03 00:00:00+11:00 # 2011-04-04: 2011-04-03 23:00:00+10:00 (wrong) dst_start_2011 = datetime.datetime(2011, 10, 2) # this would come from hobart_dt dst_start_2011 = hobart_tz.localize(dst_start_2011) next = hobart_tz.normalize(dst_start_2011 + datetime.timedelta(days=1)) print '2011-10-02:', dst_start_2011 print '2011-10-03:', next # expect 2011-10-03 00:00:00+11:00 print # gives # 2011-10-02: 2011-10-02 00:00:00+10:00 # 2011-10-03: 2011-10-03 01:00:00+11:00 (wrong) # I guess we could ignore the timezone and localise *after* ? dst_finish_2011 = datetime.datetime(2011, 4, 3) # this would come from hobart_dt next = dst_finish_2011 + datetime.timedelta(days=1) # now localise dst_finish_2011 = hobart_tz.localize(dst_finish_2011) next = hobart_tz.localize(next) print '2011-04-03:', dst_finish_2011 print '2011-04-04:', next # expect 2011-04-04 00:00:00+10:00 print # gives # 2011-04-03: 2011-04-03 00:00:00+11:00 # 2011-04-04: 2011-04-04 00:00:00+10:00 

Creo que está obteniendo este resultado porque está agregando un día en lugar de 86400 segundos. No hay una equivalencia uniforme y siempre correcta entre días y segundos. Por ejemplo, si pytz obligara a un día a “realmente” ser 86400 segundos, agregar un día a una fecha de 31 de diciembre o 30 de junio a veces causaría que el campo de segundos del resultado estuviera “a un segundo de apagado”, porque en algunos Años en esos días han tenido 86401 segundos. (Es posible que puedan tener 86402 o incluso 86399 segundos en el futuro).

Por lo tanto, agregar un día significa simplemente boost el día en uno, pasar al mes y al año si es necesario, pero sin cambiar los campos de hora del día. Intente agregar 86400 segundos y vea si obtiene el resultado deseado.

Para saber la hora de inicio del día (medianoche) y la hora de finalización del día (mañana) en la zona horaria local, saber la hora UTC:

 #!/usr/bin/env python from datetime import datetime, time, timedelta import pytz # $ pip install pytz from tzlocal import get_localzone # $ pip install tzlocal tz = get_localzone() # get the local timezone as pytz.timezone now = datetime.now(pytz.utc) # some UTC time dt = now.astimezone(tz) # the same time in the local timezone today = dt.date() # today in the local timezone (naive date object) midnight = datetime.combine(today, time()) # midnight in the local timezone aware_midnight = tz.localize(midnight, is_dst=None) # raise exception # for ambiguous or # non-existing # times tomorrow = midnight + timedelta(1) aware_tomorrow = tz.localize(tomorrow, is_dst=None) def print_time(aware_dt, fmt="%Y-%m-%d %H:%M:%S %Z%z"): print(aware_dt.strftime(fmt)) utc_dt = aware_dt.astimezone(pytz.utc) # the same time in UTC print(utc_dt.strftime(fmt)) print_time(aware_midnight) print_time(aware_tomorrow) 

Salida

 2014-09-01 00:00:00 EST+1000 2014-08-31 14:00:00 UTC+0000 2014-09-02 00:00:00 EST+1000 2014-09-01 14:00:00 UTC+0000 

Ver también,

  • ¿Cómo obtengo la hora UTC de “medianoche” para una zona horaria determinada? – sólo medianoche (no mañana)
  • ¿Cómo puedo restar un día de una fecha de python? – Sobre la diferencia entre hace un día y ayer.
  • python – datetime with timezone to epoch – demuestra lo fácil que es producir una respuesta incorrecta (para algunas fechas, zonas horarias).

Después de un poco de experimentación y pensamiento, creo que tengo una solución para ti. Mi respuesta anterior no fue correcta como usted señaló; un objeto timedelta para días = 1 es básicamente el mismo que para segundos = 86400 (excepto cuando se trata de segundos de salto).

Una forma, como recomendaría, para incrementar la fecha sin importar la hora del día es usar un objeto datetime.date lugar de un objeto datetime.datetime :

 >>> oneday = datetime.timedelta(days=1) >>> d = datetime.date(2011,4,3) >>> str(d + oneday) '2011-04-04' 

La hora del día podría agregarse para formar un objeto datetime.datetime completo en el que sabe que los campos de la hora del día no se han modificado de su valor original.

Otro método, uno de los cuales me sentiría seguro de usar, es trabajar temporalmente con fechas “ingenuas”. De esta manera no hay una política de zona horaria para aplicar al agregar el timedelta .

 >>> hob = pytz.timezone('Australia/Hobart') >>> dstlast = datetime.datetime(2011,4,3) >>> str(dstlast) '2011-04-03 00:00:00' >>> dstlasthob = hob.localize(dstlast) >>> str(dstlasthob) '2011-04-03 00:00:00+11:00' >>> oneday = datetime.timedelta(days=1) >>> str(hob.normalize(dstlasthob + oneday)) '2011-04-03 23:00:00+10:00' >>> nextday = hob.localize(dstlasthob.replace(tzinfo=None) + oneday) >>> str(nextday) '2011-04-04 00:00:00+10:00' 

Probé este método para las fechas que tienen segundos de salto (un ejemplo es 2008-12-31) y el resultado tiene la hora del día 00:00:00. Lo que realmente puede estar mal, no estoy seguro, pero es lo que quieres 🙂

El siguiente código intenta obtener un tiempo de medianoche; si el ajuste de la zona horaria hace que falle, se reajusta a la medianoche con el nuevo desplazamiento de zona.

 def DayStartEnd(localized_dt): tz = localized_dt.tzinfo start = tz.normalize(datetime.datetime(localized_dt.year,localized_dt.month,localized_dt.day,0,0,0,0,tz)) after_midnight = start.hour*60*60 + start.minute*60 + start.second if start.day != localized_dt.day: start += datetime.timedelta(seconds = 24*60*60 - after_midnight) elif after_midnight != 0: start -= datetime.timedelta(seconds = after_midnight) end = tz.normalize(start + datetime.timedelta(hours=24)) after_midnight = end.hour*60*60 + end.minute*60 + end.second if end.day == localized_dt.day: end += datetime.timedelta(seconds = 24*60*60 - after_midnight) elif after_midnight != 0: end -= datetime.timedelta(seconds = after_midnight) return start,end >>> hobart_tz = pytz.timezone('Australia/Hobart') >>> dst_finish_2011 = datetime.datetime(2011, 4, 3) >>> dst_finish_2011 = hobart_tz.localize(dst_finish_2011) >>> start,end = DayStartEnd(dst_finish_2011) >>> print start,end 2011-04-03 00:00:00+11:00 2011-04-04 00:00:00+10:00 >>> dst_start_2011 = datetime.datetime(2011, 10, 2) >>> dst_start_2011 = hobart_tz.localize(dst_start_2011) >>> start,end = DayStartEnd(dst_start_2011) >>> print start,end 2011-10-02 00:00:00+10:00 2011-10-03 00:00:00+11:00