Alexander Belopolsky <alexander.belopol...@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 _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com