[Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones (original) (raw)
Akira Li 4kir4.1i at gmail.com
Thu Apr 16 00:02:48 CEST 2015
- Previous message (by thread): [Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones
- Next message (by thread): [Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Alexander Belopolsky <alexander.belopolsky at gmail.com> writes:
... For most world locations past discontinuities are fairly well documented for at least a century and future changes are published with at least 6 months lead time.
It is important to note that the different versions of the tz database may lead to different tzinfo (utc offset, tzname) even for past dates.
i.e., (lt, tzid, isdst) is not enough because the result for (lt, tzid(2015b), isdst) may be different from (lt, tzid(X), isdst) where
lt = local time e.g., naive datetime tzid = timezone from the tz database e.g., Europe/Kiev isdst = a boolean flag for disambiguation X != 2015b
In other words, a fixed utc offset might not be sufficient even for past dates.
... Moreover, a program that rejects invalid times on input, but stores them for a long time may see its database silently corrupted after a zoneinfo update.
Now it is time to make specific proposal. I would like to extend datetime.astimezone() method to work on naive datetime instances. Such instances will be assumed to be in local time and discontinuities will be handled as follows:
1. wall(t) == lt has a single solution. This is the trivial case and lt.astimezone(utc) and lt.astimezone(utc, which=i) for i=0,1 should return that solution. 2. wall(t) == lt has two solutions t1 and t2 such that t1 < t2. In this case lt.astimezone(utc) == lt.astimezone(utc, which=0) == t1 and lt.astimezone(utc, which=1) == t2.
In pytz terms: which = not isdst
(end-of-DST-like transition: isdst
changes from True to False in the direction of utc time).
It resolves AmbiguousTimeError raised by tz.localize(naive, is_dst=None)
.
3. wall(t) == lt has no solution. This happens when there is UTC time t0 such that wall(t0) < lt and wall(t0+epsilon) > lt (a positive discontinuity at time t0). In this case lt.astimezone(utc) should return t0 + lt - wall(t0). I.e., we ignore the discontinuity and extend wall(t) linearly past t0. Obviously, in this case the invariant wall(lt.astimezone(utc)) == lt won't hold. The "which" flag should be handled as follows: lt.astimezone(utc) == lt.astimezone(utc, which=0) and lt.astimezone(utc, which=0) == t0 + lt - wall(t0+eps).
It is inconsistent with the previous case: here which = isdst
but
which = not isdst
above.
lt.astimezone(utc, which=0) == t0 + lt - wall(t0+eps)
corresponds to:
result = tz.normalize(tz.localize(lt, isdst=False))
i.e., which = isdst
(t0 is at the start of DST and therefore isdst
changes from False to True).
It resolves NonExistentTimeError raised by tz.localize(naive, is_dst=None)
. start-of-DST-like transition ("Spring forward").
For example,
from datetime import datetime, timedelta import pytz
tz = pytz.timezone('America/New_York')
2am -- non-existent time
print(tz.normalize(tz.localize(datetime(2015, 3, 8, 2), is_dst=False)))
-> 2015-03-08 03:00:00-04:00 # after the jump (wall(t0+eps))
print(tz.localize(datetime(2015, 3, 8, 3), is_dst=None))
-> 2015-03-08 03:00:00-04:00 # same time, unambiguous
2:01am -- non-existent time
print(tz.normalize(tz.localize(datetime(2015, 3, 8, 2, 1), is_dst=False)))
-> 2015-03-08 03:01:00-04:00
print(tz.localize(datetime(2015, 3, 8, 3, 1), is_dst=None))
-> 2015-03-08 03:01:00-04:00 # same time, unambiguous
2:59am non-existent time
dt = tz.normalize(tz.localize(datetime(2015, 3, 8, 2, 59), is_dst=True)) print(dt)
-> 2015-03-08 01:59:00-05:00 # before the jump (wall(t0-eps))
print(tz.normalize(dt + timedelta(minutes=1)))
-> 2015-03-08 03:00:00-04:00
With the proposed features in place, one can use the naive code
t = lt.astimezone(utc) and get predictable behavior in all cases and no crashes. A more sophisticated program can be written like this: t1 = lt.astimezone(utc, which=0) t2 = lt.astimezone(utc, which=1) if t1 == t2: t = t1 elif t2 > t1: # ask the user to pick between t1 and t2 or raise AmbiguousLocalTimeError else: t = t1 # warn the user that time was invalid and changed or raise InvalidLocalTimeError
- Previous message (by thread): [Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones
- Next message (by thread): [Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]