I’m working on a site called localhours.com that displays store opening hours across the US. Although previously I just used the browser’s client time and opening hour data in local time to display if a store was open or closed, this was obviously not sufficient (and tricky to do, since a browser request often doesn’t include a timestamp).
What makes this an interesting problem?
Timezones run through states and possibly through cities. At least a zipcode-timezone mapping is required.
A small portion of zipcodes are constantly changing apparently, and there is no database that can guarantee a 100% match between a zipcode and a timezone (and least for free). There are some data sets that provide ~98% accurate content though (free), so I spend some time figuring out which one to download. I settled on this link.
Not all states (zipcodes?) treat daylight savings in the same way. Luckily, since 2007 (in the US), DST starts at the same time everywhere: 2am on the second Sunday in March, and ends at 2am DST on the first Sunday of November. However, there are a few states that don’t have daylight savings.
In the end I:
- stored the local store timezone (tz_offset) along with bool (dst) indicating if daylight savings apply or not in the db,
- calculed the local time using datetime.utcnow() + tz_offset, and
- ran the result through a helper function that checked if dst should be applied for this time of the year.
The following module helped with the latter (mostly taken from the Python datetime docs):
from datetime import timedelta, datetime def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += timedelta(days_to_go) return dt # For a complete and up-to-date set of DST rules and timezone definitions, # visit the Olson Database: http://www.twinsun.com/tz/tz-link.htm or # http://sourceforge.net/projects/pytz/ # In the US, since 2007, DST starts at 2am (standard time) on the second # Sunday in March, which is the first Sunday on or after Mar 8. DSTSTART_2007 = datetime(1, 3, 8, 2) # and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. DSTEND_2007 = datetime(1, 11, 1, 1) # From 1987 to 2006, DST used to start at 2am (standard time) on the first # Sunday in April and to end at 2am (DST time; 1am standard time) on the last # Sunday of October, which is the first Sunday on or after Oct 25. DSTSTART_1987_2006 = datetime(1, 4, 1, 2) DSTEND_1987_2006 = datetime(1, 10, 25, 1) # From 1967 to 1986, DST used to start at 2am (standard time) on the last # Sunday in April (the one on or after April 24) and to end at 2am (DST time; # 1am standard time) on the last Sunday of October, which is the first Sunday # on or after Oct 25. DSTSTART_1967_1986 = datetime(1, 4, 24, 2) DSTEND_1967_1986 = DSTEND_1987_2006 def dst(dt, dst): if not dst: return dt # Find start and end times for US DST. For years before 1967, return dt if 2006 < dt.year: dststart, dstend = DSTSTART_2007, DSTEND_2007 elif 1986 < dt.year < 2007: dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006 elif 1966 < dt.year < 1987: dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 else: return dt start = first_sunday_on_or_after(dststart.replace(year=dt.year)) end = first_sunday_on_or_after(dstend.replace(year=dt.year)) if start <= dt < end: return timedelta(hours=1) + dt else: return dt