Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle DST and TZ in recurring events?

Does dateutil rrule support DST and TZ? Need something similar to iCalendar RRULE.

If not - how to tackle this problem (scheduling recurring events & DST offset change)

Imports

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

Issue with timedelta (need to have the same local hours, but different DST offsets):

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

Issue with rrule (need to have the same every local hour of each occurrence):

>>> 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>)
like image 234
g00fy Avatar asked Sep 19 '12 23:09

g00fy


Video Answer


1 Answers

@asdf: I can't add code to comments so I need to post this as an answer:

I am afraid that with your solution I will always loose DST info, therefore half of the year recurrences would be 1 hour off time.

Basing on your answer I found out that this might be the correct solution:

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

This is why I think your solution might fail:

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

As you can see, there is 1 hour difference between the rrule result, and the real data we store in the DB.

like image 87
g00fy Avatar answered Sep 17 '22 12:09

g00fy