I was expecting that the following two would give the same result, but they didn't. Why is that the case?
Versions:
pytz==2018.5
python-dateutil==2.7.3
import datetime
import pytz
tz = pytz.timezone('Pacific/Apia')
today_utc = datetime.datetime(2011, 12, 30, 9, 59,
tzinfo=datetime.timezone.utc)
today_tz = today_utc.astimezone(tz)
print(today_tz.isoformat())
prints 2011-12-29T23:59:00-10:00
(which is correct)
import datetime
import dateutil.tz
tz = dateutil.tz.gettz('Pacific/Apia')
today_utc = datetime.datetime(2011, 12, 30, 9, 59,
tzinfo=datetime.timezone.utc)
today_tz = today_utc.astimezone(tz)
print(today_tz.isoformat())
prints 2011-12-29T23:59:00+14:00
(which is wrong)
You have discovered a bug in dateutil
, which I have now reported and fixed.
The bug was caused by an issue with how the "wall time" of transitions were calculated in dateutil
, which was making some assumptions that do not hold when a time zone's base offset changes during DST. Expanding your example a bit:
from datetime import datetime, timedelta
from dateutil import tz
import pytz
APIA = tz.gettz('Pacific/Apia')
APIA_p = pytz.timezone('Pacific/Apia')
dt0 = datetime.fromisoformat('2011-12-29T20:00-10:00')
for i in range(5):
dt = (dt0 + timedelta(hours=i))
dt_d = dt.astimezone(APIA)
dt_p = dt.astimezone(APIA_p)
print(f'{dt_d.isoformat()}, {dt_p.isoformat()}')
## Result:
# 2011-12-29T20:00:00-10:00, 2011-12-29T20:00:00-10:00
# 2011-12-29T21:00:00-10:00, 2011-12-29T21:00:00-10:00
# 2011-12-29T22:00:00-10:00, 2011-12-29T22:00:00-10:00
# 2011-12-29T23:00:00+14:00, 2011-12-29T23:00:00-10:00
# 2011-12-31T00:00:00+14:00, 2011-12-31T00:00:00+14:00
You can see that dateutil
always calculates the date and time correctly, but when isoformat
calls utcoffset
, the offset change happens 1 hour early. This is because astimezone
calls tzinfo.fromutc
under the hood, while isoformat
calls utcoffset
. dateutil
stores the transition times in both UTC and local time, the UTC times are used in fromutc
and the local times are used in utcoffset
, dst
and tzname
. This bug involved over-compensating for DST when calculating the "wall time" of the transition during DST->DST transitions (which are exceedingly rare), which is why it didn't affect astimezone
.
Bottom line - you are using both pytz
and dateutil
correctly, and this error will be fixed in the next release.
Note: This answer was edited after I found the cause of and fix for the bug.