Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@katylava
Last active December 28, 2015 13:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save katylava/7507328 to your computer and use it in GitHub Desktop.
Save katylava/7507328 to your computer and use it in GitHub Desktop.

Datetimes and Timezones and DST, oh my!

This is an example of how to generate occurrences from python-dateutil's rrule module, and convert them properly to UTC before saving to the database, particularly if your input is localized to a timezone that observes daylight savings. This assumes your server time is localized, otherwise if it's UTC you probably won't run into these problems.

Note: US daylight savings in 2014 started on March 9

import pytz
from dateutil.parser import *
from dateutil.rrule import rrule, WEEKLY, MO


tz = pytz.timezone('America/Chicago')
start = parse('Feb 15 2014, 11am')
end = parse('March 24 2014')

_dates = rrule(WEEKLY, dtstart=start, until=end, byweekday=(MO,))
dates = list(_dates)

print dates

out:

[datetime.datetime(2014, 2, 17, 11, 0), 
 datetime.datetime(2014, 2, 24, 11, 0), 
 datetime.datetime(2014, 3, 3, 11, 0), 
 datetime.datetime(2014, 3, 10, 11, 0), 
 datetime.datetime(2014, 3, 17, 11, 0)]

These are naive datetimes, which is good, because we want them to be consitently at 11am regardless of whether we're in daylight savings time or not. Now we can localize each one to US Central and they will all stay at 11am.

localized = [tz.localize(dt) for dt in dates]
for dt in localized:
    print 'Central: {}; UTC: {}'.format(dt, pytz.utc.normalize(dt))

out:

Central: 2014-02-17 11:00:00-06:00; UTC: 2014-02-17 17:00:00+00:00
Central: 2014-02-24 11:00:00-06:00; UTC: 2014-02-24 17:00:00+00:00
Central: 2014-03-03 11:00:00-06:00; UTC: 2014-03-03 17:00:00+00:00
Central: 2014-03-10 11:00:00-05:00; UTC: 2014-03-10 16:00:00+00:00
Central: 2014-03-17 11:00:00-05:00; UTC: 2014-03-17 16:00:00+00:00

The left is what should be displayed to the user, but the right is what should be saved to the database. However, you can't convert directly to UTC, because this is what would happen:

utcified = [pytz.utc.localize(dt) for dt in dates]
for dt in utcified:
    print 'Central: {}; UTC: {}'.format(tz.normalize(dt), dt)

out:

Central: 2014-02-17 05:00:00-06:00; UTC: 2014-02-17 11:00:00+00:00
Central: 2014-02-24 05:00:00-06:00; UTC: 2014-02-24 11:00:00+00:00
Central: 2014-03-03 05:00:00-06:00; UTC: 2014-03-03 11:00:00+00:00
Central: 2014-03-10 06:00:00-05:00; UTC: 2014-03-10 11:00:00+00:00
Central: 2014-03-17 06:00:00-05:00; UTC: 2014-03-17 11:00:00+00:00

The obvious problem is our 11am local event is now scheduled for 5pm. However, assuming for a moment that what we really wanted was an event to occure at 5pm US Central every Monday, you can see that now when DST goes into effect, it gets changed to 6pm, which is not what we want.

Steps to generate database-persistant event occurrences from a recurring rule respecting DST

  1. Request user input includes a time zone preference (or for local events, ensure you have a constant which stores the local timezone)
  2. Localize the naive start/end dates and the naive rrule-generated dates to the configured timezone. It's possible your start/end dates will have tzinfo on them (if it's coming from a Django form, for example), and you may need to remove that.
  3. Use pytz's normalize method to convert the dates to UTC and save to the database

Steps to display occurrences retrieved from database in the appropriate timezone

  1. Create a pytz timezone object for the user's or site's configured timezone
  2. Call the timezone object's normalize method, passing each UTC date in, to get the local representation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment