I've done a quite a bit of research before posting this, but I can't seem to get the conversion right. I have some data which has timestamps, and some have DST applied, and others don't. I thought the correct way to specify that it's without DST is using the is_dst
parameter for pytz. All 3 options give the same offset from UTC, which is incorrect. The offset should be +1000. What's the best way to do this conversion, and why does the is_dst parameter not make any difference?
pytz_eastern.localize(datetime(2018, 1, 18, 18, 50), is_dst=None).strftime('%Y-%m-%d %H:%M %z')
'2018-01-18 18:50 +1100'
pytz_eastern.localize(datetime(2018, 1, 18, 18, 50), is_dst=False).strftime('%Y-%m-%d %H:%M %z')
'2018-01-18 18:50 +1100'
pytz_eastern.localize(datetime(2018, 1, 18, 18, 50), is_dst=True).strftime('%Y-%m-%d %H:%M %z')
'2018-01-18 18:50 +1100'
The is_dst
parameter is ignored for most timestamps. It is only used during DST transition ambiguous periods to resolve that ambiguity.
You're trying to convert a datetime by ignoring the transition rules. I don't think pytz will support that. Instead you can pick a date in standard time and ask for its offset, then use that.
>>> from datetime import *
>>> import pytz
>>> pytz_eastern = pytz.timezone('Australia/Sydney')
The utcoffset
method gives the offset for a particular datetime (and the dst
method will also give just the DST offset).
>>> pytz_eastern.dst(datetime(2018, 6, 1))
datetime.timedelta(0)
>>> pytz_eastern.utcoffset(datetime(2018, 6, 1))
datetime.timedelta(0, 36000)
>>> pytz_eastern.dst(datetime(2018, 1, 1))
datetime.timedelta(0, 3600)
>>> pytz_eastern.utcoffset(datetime(2018, 1, 1))
datetime.timedelta(0, 39600)
Take the utcoffset from a date in standard time and set it directly with the tzinfo
kwarg of datetime, and only then give it to pytz for conversion.
So here's a datetime that was shown on a clock which was not adjusted for DST:
>>> standard_offset = timezone(pytz_eastern.utcoffset(datetime(2018, 6, 1)))
>>> datetime(2018, 1, 18, 18, 50, tzinfo=standard_offset).strftime('%Y-%m-%d %H:%M %z')
'2018-01-18 18:50 +1000'
And here's that same datetime brought back into reality:
>>> datetime(2018, 1, 18, 18, 50, tzinfo=standard_offset).astimezone(pytz_eastern).strftime('%Y-%m-%d %H:%M %z')
'2018-01-18 19:50 +1100'
(The standard offset also seems to be available as ._utcoffset
, but that's not documented, so that's a reason to ask for the utcoffset of a specific date, as it's less likely for offsets in the past to ever change.)
In fact, since pytz gives you the computed offset and the current DST value, you could subtract the two to get the standard offset ignoring DST.
def add_forgotten_dst(dt, zoneinfo):
'''Like pytz.localize, but ignores dst.'''
utcoffset = zoneinfo.utcoffset(dt)
dst = zoneinfo.dst(dt)
standard_offset = utcoffset - dst
dt = dt.replace(tzinfo=timezone(standard_offset))
return dt.astimezone(zoneinfo)
>>> naive_time = datetime(2018, 1, 18, 18, 50)
>>> print(pytz_eastern.localize(naive_time))
2018-01-18 18:50:00+11:00
>>> print(add_forgotten_dst(naive_time, pytz_eastern))
2018-01-18 19:50:00+11:00