Reputation: 18418
I am curious about how to call the "canonical" timezone offset that is offered on certain timezone selectors (if there's even such a thing, as a canonical offset, which I'm not even sure of).
For instance, on Windows XP, you can see that for Eastern Time (US & Canada)
the drop-down always shows GMT-05:00
This is actually not correct for the whole year, since when daylight savings time is in effect, the offset with UTC
is only -4:00
. On the other hand, everyone mentions US/Eastern
as being 5 hours off from UTC. I was wondering how that -5:00
is called. Email Time Identifier? Canonical timezone offset?
Also, if I create a US/Eastern
timezone with pytz, is there a way of grabbing that -5:00
regardless of whether the actual now time is in DST or not? I mean... I'd like to know if there's a function, or... something to get -5:00
, regardless of whether I'm running that today or in the middle of August (when DST
is enabled and the actual offset is just -4:00
)
Thank you in advance.
Upvotes: 4
Views: 1629
Reputation: 879083
If we take "canonical" to mean the utcoffset of dates that are not in DST, then the problem is reduced to finding dates (for each timezone) which are not DST.
We could try the current date first. If it is not DST, then we are in luck. If it is, then we could step through the list of utc transition dates (which are stored in tzone._utc_transition_times
) until we find one that is not DST:
import pytz
import datetime as DT
utcnow = DT.datetime.utcnow()
canonical = dict()
for name in pytz.all_timezones:
tzone = pytz.timezone(name)
try:
dstoffset = tzone.dst(utcnow, is_dst=False)
except TypeError:
# pytz.utc.dst does not have a is_dst keyword argument
dstoffset = tzone.dst(utcnow)
if dstoffset == DT.timedelta(0):
# utcnow happens to be in a non-DST period
canonical[name] = tzone.localize(utcnow, is_dst=False).strftime('%z')
else:
# step through the transition times until we find a non-DST datetime
for transition in tzone._utc_transition_times[::-1]:
dstoffset = tzone.dst(transition, is_dst=False)
if dstoffset == DT.timedelta(0):
canonical[name] = (tzone.localize(transition, is_dst=False)
.strftime('%z'))
break
for name, utcoffset in canonical.iteritems():
print('{} --> {}'.format(name, utcoffset))
# All timezones have been accounted for
assert len(canonical) == len(pytz.all_timezones)
yields
...
Mexico/BajaNorte --> -0800
Africa/Kigali --> +0200
Brazil/West --> -0400
America/Grand_Turk --> -0400
Mexico/BajaSur --> -0700
Canada/Central --> -0600
Africa/Lagos --> +0100
GMT-0 --> +0000
Europe/Sofia --> +0200
Singapore --> +0800
Africa/Tripoli --> +0200
America/Anchorage --> -0900
Pacific/Nauru --> +1200
Note that the code above accesses the private attribute tzone._utc_transition_times
. This is an implementation detail in pytz. Since it is not part of the public API, it is not guaranteed to exist in future versions of pytz. Indeed, it does not even exist for all timezones in the current version of pytz -- in particular, it does not exist for timezones that have no DST transition times, such as 'Africa/Bujumbura'
for example. (That's why I bother to check if utcnow happens to be in a non-DST time period first.)
If you'd like a method which does not rely on private attributes, we could instead simply march utcnow
back one day until we find a day which is in a non-DST time period. The code would be a bit slower than the one above, but since you really only have to run this code once to glean the desired information, it really should not matter.
Here is what the code would look like without using _utc_transition_times
:
import pytz
import datetime as DT
utcnow = DT.datetime.utcnow()
canonical = dict()
for name in pytz.all_timezones:
tzone = pytz.timezone(name)
try:
dstoffset = tzone.dst(utcnow, is_dst=False)
except TypeError:
# pytz.utc.dst does not have a is_dst keyword argument
dstoffset = tzone.dst(utcnow)
if dstoffset == DT.timedelta(0):
# utcnow happens to be in a non-DST period
canonical[name] = tzone.localize(utcnow, is_dst=False).strftime('%z')
else:
# step through the transition times until we find a non-DST datetime
date = utcnow
while True:
date = date - DT.timedelta(days=1)
dstoffset = tzone.dst(date, is_dst=False)
if dstoffset == DT.timedelta(0):
canonical[name] = (tzone.localize(date, is_dst=False)
.strftime('%z'))
break
for name, utcoffset in canonical.iteritems():
print('{} --> {}'.format(name, utcoffset))
# All timezones have been accounted for
assert len(canonical) == len(pytz.all_timezones)
Upvotes: 3