2012-09-19 11 views
11

¿Admite dateutil soporte DST y TZ? Necesita algo similar a iCalendar RRULE.¿Cómo manejar DST y TZ en eventos recurrentes?

Si no - cómo hacer frente a este problema (la programación de los eventos periódicos & DST cambio offset)

importaciones

>>> from django.utils import timezone 
>>> import pytz 
>>> from datetime import timedelta 
>>> from dateutil import rrule 
>>> now = timezone.now() 
>>> pl = pytz.timezone("Europe/Warsaw") 

Problema con timedelta (necesitan tener las mismas horas locales, pero distintos desplazamientos DST) :

>>> pl.normalize(now) 
datetime.datetime(2012, 9, 20, 1, 16, 58, 226000, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>)  
>>> pl.normalize(now+timedelta(days=180)) 
datetime.datetime(2013, 3, 19, 0, 16, 58, 226000, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>) 

Problema con RRULE (necesita tener el mismo cada hora local de cada ocurrencia):

>>> r = rrule.rrule(3,dtstart=now,interval=180,count=2) 
>>> pl.normalize(r[0]) 
datetime.datetime(2012, 9, 20, 1, 16, 58, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>) 
>>> pl.normalize(r[1]) 
datetime.datetime(2013, 3, 19, 0, 16, 58, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>) 
+0

para conocer las mejores prácticas sobre el horario de verano y las zonas horarias, este http://stackoverflow.com/q/2532729/1167333 ofrece un buen resumen de las mejores prácticas – oberron

Respuesta

10

@asdf: No puedo agregar el código en los comentarios, así que necesito para publicar esto como una respuesta:

me temo que con su solución lo haré información DST siempre suelto, por lo tanto, la mitad de las recurrencias año sería ser 1 hora de tiempo libre.

Basándose en su respuesta me di cuenta de que esto podría ser la solución correcta:

>>> from datetime import datetime 
>>> import pytz 
>>> from dateutil import rrule 
>>> # this is raw data I get from the DB, according to django docs I store it in UTC 
>>> raw = datetime.utcnow().replace(tzinfo=pytz.UTC) 
>>> # in addition I need to store the timezone so I can do dst the calculations 
>>> tz = pytz.timezone("Europe/Warsaw") 
>>> # this means that the actual local time would be 
>>> local = raw.astimezone(tz) 
>>> # but rrule doesn't take into account DST and local time, so I must convert aware datetime to naive 
>>> naive = local.replace(tzinfo=None) 
>>> # standard rrule 
>>> r = rrule.rrule(rrule.DAILY,interval=180,count=10,dtstart=naive) 
>>> for dt in r: 
>>>  # now we must get back to aware datetime - since we are using naive (local) datetime, 
     # we must convert it back to local timezone 
...  print tz.localize(dt) 

Es por esto que creo que su solución puede fallar:

>>> from datetime import datetime 
>>> from dateutil import rrule 
>>> import pytz 
>>> now = datetime.utcnow() 
>>> pl = pytz.timezone("Europe/Warsaw") 
>>> r = rrule.rrule(rrule.DAILY, dtstart=now, interval=180, count=2) 
>>> now 
datetime.datetime(2012, 9, 21, 9, 21, 57, 900000) 
>>> for dt in r: 
...  local_dt = dt.replace(tzinfo=pytz.UTC).astimezone(pl) 
...  print local_dt - local_dt.dst() 
...  
2012-09-21 10:21:57+02:00 
2013-03-20 10:21:57+01:00 
>>> # so what is the actual local time we store in the DB ? 
>>> now.replace(tzinfo=pytz.UTC).astimezone(pl) 
datetime.datetime(2012, 9, 21, 11, 21, 57, 900000, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>) 

Como se puede ver, hay 1 hora de diferencia entre el resultado de regla y los datos reales que almacenamos en la base de datos.

+0

Esto parece correcto, pero no puedo creer que no haya una manera mejor para implementar esto – Jakobovski

+0

estaba golpeando mi cabeza en esto todo el día - gracias @ g00fy! – mstringer

3

Tenga en cuenta que lo que django.utils.timezone.now() devuelve puede ser una fecha de inicio ingenua o consciente, dependiendo de su configuración USE_TZ. Lo que debe usar internamente para los cálculos (por ejemplo, el now que proporciona a rrule.rrule) es un horario de UTC. Puede ser uno de detección offset (es decir, datetime.now(pytz.UTC)), o uno ingenuo (es decir, datetime.utcnow()). Este último parece ser el preferido para el almacenamiento (ver this blogpost).

Ahora, rrule.rrule maneja las zonas horarias, es por eso que observa el cambio CEST-to-CET en lo que produce su rrule. Sin embargo, si lo que quiere es obtener siempre la misma hora (por ejemplo, 0 AM todos los días, sin importar el horario de verano o no), entonces realmente quiere "ignorar" el cambio. Una forma de hacerlo sería hacer dt = dt - dt.dst(), si dt fuera una fecha de atención.

Así es como se puede hacer eso:

from datetime import datetime 
from dateutil import rrule 
import pytz 
now = datetime.utcnow() 
pl = pytz.timezone("Europe/Warsaw") 
r = rrule.rrule(rrule.DAILY, dtstart=now, interval=180, count=2) 

# will yield naive datetimes, assumed UTC 
for dt in r: 
    # convert from naive-UTC to aware-local 
    local_dt = dt.replace(tzinfo=pytz.UTC).astimezone(pl) 
    # account for the dst difference 
    print local_dt - local_dt.dst() 

Esto imprime dos datetimes, cada uno está en una zona horaria diferente (bueno, diverso ajuste del horario de verano), ambos representan la misma hora wallclock. Si tuviera que manejar las fechas UTC-awaretimes en lugar de naive-assume-UTC como en el ejemplo, simplemente omitiría la parte .replace. Se puede encontrar una hoja de referencia rápida sobre estas conversiones en here.

2

Sí, el punto es que NO debe almacenar el tiempo de carga, nunca. Almacene el UTC y conviértalo al tiempo local a pedido (es decir, por solicitud, utilizando los datos de solicitud, como el encabezado Aceptar Idioma, para saber qué debe usar).

Lo que está haciendo es utilizar una fecha localizada para los cálculos (es decir, rrule.rrule()).Esto no es óptimo, ya que necesita conocer la zona horaria objetivo para hacerlo, por lo que esto se puede hacer solo por solicitud, en lugar de precalcular las realizaciones de regla. Es por eso que debe usar UTC internamente (es decir, para precalcular las fechas) y luego convertirlas antes de enviarlas al usuario. En este caso, solo la conversión debería realizarse después de recibir la solicitud (es decir, cuando se conoce la zona horaria de destino).

Cuestiones relacionadas