[Python-Dev] Status on PEP-431 Timezones
Akira Li
4kir4.1i at gmail.com
Wed Apr 15 21:53:16 CEST 2015
Lennart Regebro <regebro at gmail.com> writes:
> OK, so I realized another thing today, and that is that arithmetic
> doesn't necessarily round trip.
>
> For example, 2002-10-27 01:00 US/Eastern comes both in DST and STD.
>
> But 2002-10-27 01:00 US/Eastern STD minus two days is 2002-10-25 01:00
> US/Eastern DST
"two days" is ambiguous here. It is incorrect if you mean 48 hours (the
difference is 49 hours):
#!/usr/bin/env python3
from datetime import datetime, timedelta
import pytz
tz = pytz.timezone('US/Eastern')
then_isdst = False # STD
then = tz.localize(datetime(2002, 10, 27, 1), is_dst=then_isdst)
now = tz.localize(datetime(2002, 10, 25, 1), is_dst=None) # no utc transition
print((then - now) // timedelta(hours=1))
# -> 49
> However, 2002-10-25 01:00 US/Eastern DST plus two days is 2002-10-27
> 01:00 US/Eastern, but it is ambiguous if you want DST or not DST.
It is not ambiguous if you know what "two days" *in your particular
application* should mean (`day+2` vs. +48h exactly):
print(tz.localize(now.replace(tzinfo=None) + timedelta(2), is_dst=then_isdst))
# -> 2002-10-27 01:00:00-05:00 # +49h
print(tz.normalize(now + timedelta(2))) # +48h
# -> 2002-10-27 01:00:00-04:00
Here's a simple mental model that can be used for date arithmetics:
- naive datetime + timedelta(2) == "same time, elapsed hours unknown"
- aware utc datetime + timedelta(2) == "same time, +48h"
- aware datetime with timezone that may have different utc offsets at
different times + timedelta(2) == "unknown time, +48h"
"unknown" means that you can't tell without knowning the specific
timezone.
It ignores leap seconds.
The 3rd case behaves *as if* the calculations are performed using these
steps (the actual implementation may be different):
1. convert an aware datetime
object to utc (dt.astimezone(pytz.utc))
2. do the simple arithmetics using utc time
3. convert the result to the original pytz timezone (utc_dt.astimezone(tz))
you don't need `.localize()`, `.normalize()` calls here.
> And you can't pass in a is_dst flag to __add__, so the arithmatic must
> just pick one, and the sensible one is to keep to the same DST.
>
> That means that:
>
> tz = get_timezone('US/Eastern')
> dt = datetimedatetime(2002, 10, 27, 1, 0, tz=tz, is_dst=False)
> dt2 = dt - 420 + 420
> assert dt == dt2
>
> Will fail, which will be unexpected for most people.
>
> I think there is no way around this, but I thought I should flag for
> it. This is a good reason to do all your date time arithmetic in UTC.
>
> //Lennart
It won't fail:
from datetime import datetime, timedelta
import pytz
tz = pytz.timezone('US/Eastern')
dt = tz.localize(datetime(2002, 10, 27, 1), is_dst=False)
delta = timedelta(seconds=420)
assert dt == tz.normalize(tz.normalize(dt - delta) + delta)
The only reason `tz.normalize()` is used so that tzinfo would be correct
for the resulting datetime object; it does not affect the comparison otherwise:
assert dt == (dt - delta + delta) #XXX tzinfo may be incorrect
assert dt == tz.normalize(dt - delta + delta) # correct tzinfo for the final result
More information about the Python-Dev
mailing list