I never really cared about daylight saving time. Sure, I knew it existed, occasionally saw some articles making a case that it's not helpful in any way, but as long as my computer and phone knew when to switch - I was fine with it. One hour forward here, one hour back there, whatever. But I never really considered its practical implications, such as date management in software - like in this particular case.
A couple of weeks ago, a colleague of mine pointed out that when he copied events on our company instance of Schedoodle (which is our work schedule web app, built on Django) from week 21-27.03 onto the next one, some of them were off by one hour. I quickly found out that 27.03 is the magical day when daylight saving time change occurs, and 01:59:59 jumps to 03:00:00. It was obvious that this was the cause of the problem, so I had a starting point.
First, I looked at the code responsible for copying events from the previous week:
one_week = timedelta(weeks=1) for prev_event in prev_week_events: start_datetime = prev_event.start_datetime + one_week end_datetime = prev_event.end_datetime + one_week ...
Nothing fancy - it just adds
timedelta(weeks=1) to start and end
Hmm. Let's peek into the database -
DateTimeField is smartly converted to UTC,
and copying behaved as I thought it should -
2016-03-21 10:00:00 plus one week
2016-03-28 10:00:00. Correct, isn't it? If only... Even though the timezone
doesn't change, the hour offset for those dates does - 21.03.2016 is CET UTC+1h
and 28.03.2016 is CEST UTC+2h. So when the
datetimes are localized by Django,
they get converted to
2016-03-21 11:00:00 and
respectively. So I (kinda) know what's wrong but I still don't know which piece
of code should be changed. When in doubt, Google it.
Both 'python daylight savings time' and 'django daylight savings time' point to a lot of similar Stack Overflow questions and blog posts but I would like to highlight those that helped me the most. First - and I should probably start with it both when the issue occurred and in this post - is Django's Docs about time zones. It's quite long, but mostly because the subject itself isn't trivial. It also points to pytz which is recommended by Django when more than basic timezone support is required.
But the key resource for me was Time Zones in Pytz & Django by Tommi Kaikkonen. I can't recommend it enough. It's quite lengthy but that's because it really exhausts the topic with multiple diagrams and code samples. It covers DST itself and then goes into explaining why it's such a buggy idea ending with examples of Python code with pytz and then how that all falls into Django itself. Also helpful - Troubles with timezones by Tikitu de Jager.
The essential part (for me) to understand from those links in this case was the
difference between naive and aware
datetimes and how that affects time
addition operations with regards to DST and timezones. Both pytz and Django help
a lot and have all the required pieces (have you seen
django.utils.timezone?) - you just need to be, ekhm, aware of what
you're working with and what you want to accomplish (I know, some shocking,
With all that newly acquired knowledge, I went back and reviewed all the code
datetime manipulation. It wasn't bad (I was adding
datetime on creation instead of
timezone.make_aware()), but it definitely could be made better - and now
it is. As to the problem itself - understanding it was the main part. Most
importantly, always operate on UTC
datetimes and work it up from there.
With that in mind, all it took were just a few simple changes:
one_week = timedelta(weeks=1) for prev_event in prev_week_events: # Yay daylight savings time! tz = timezone.get_current_timezone() start_datetime = tz.localize( timezone.make_naive(prev_event.start_datetime) + one_week ) end_datetime = tz.localize( timezone.make_naive(prev_event.end_datetime) + one_week ) ...
Well, at least now I do have an opinion about daylight saving time. I don't like it.