Pytz convert time to UTC without DST

2019-08-18 16:41发布

问题:

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'

回答1:

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