[Python-Dev] Aware datetime from naive local time Was: Status on PEP-431 Timezones

Nick Coghlan ncoghlan at gmail.com
Fri Apr 10 12:38:54 CEST 2015


On 9 Apr 2015 23:15, "Alexander Belopolsky" <alexander.belopolsky at gmail.com>
wrote:
>
> Sorry for a truncated message.  Please scroll past the quoted portion.
>
> On Thu, Apr 9, 2015 at 10:21 PM, Alexander Belopolsky <
alexander.belopolsky at gmail.com> wrote:
>>
>>
>> On Thu, Apr 9, 2015 at 4:51 PM, Isaac Schwabacher <ischwabacher at wisc.edu>
wrote:
>>>
>>> > > > Well, you are right, but at least we do have a localtime utility
hidden in the email package:
>>> > > >
>>> > > > >>> from datetime import *
>>> > > > >>> from email.utils import localtime
>>> > > > >>> print(localtime(datetime.now()))
>>> > > > 2015-04-09 15:19:12.840000-04:00
>>> > > >
>>> > > > You can read <http://bugs.python.org/issue9527> for the reasons
it did not make into datetime.
>>> > >
>>> > > But that's restricted to the system time zone. Nothing good ever
comes from the system time zone...
>>> >
>>> > Let's solve one problem at a time. ...
>>>
>>> PEP 431 proposes to import zoneinfo into the stdlib, ...
>>
>>
>> I am changing the subject so that we can focus on one question without
diverting to PEP-size issues that are better suited for python ideas.
>>
>> I would like to add a functionality to the datetime module that would
solve a seemingly simple problem: given a naive datetime instance assumed
to be in local time, construct the corresponding aware datetime object with
tzinfo set to an appropriate fixed offset datetime.timezone instance.
>>
>> Python 3 has this functionality implemented in the email package since
version 3.3, and it appears to work well even
>> in the ambiguous hour
>>
>> >>> from email.utils import localtime
>> >>> from datetime import datetime
>> >>> localtime(datetime(2014,11,2,1,30)).strftime('%c %z %Z')
>> 'Sun Nov  2 01:30:00 2014 -0400 EDT'
>> >>> localtime(datetime(2014,11,2,1,30), isdst=0).strftime('%c %z %Z')
>> 'Sun Nov  2 01:30:00 2014 -0500 EST'
>>
>> However, in a location with a more interesting history, you can get a
situation that
>
>
> would look like this in the zoneinfo database:
>
> $ zdump -v  -c 1992 Europe/Kiev
> ...
> Europe/Kiev  Sat Mar 24 22:59:59 1990 UTC = Sun Mar 25 01:59:59 1990 MSK
isdst=0
> Europe/Kiev  Sat Mar 24 23:00:00 1990 UTC = Sun Mar 25 03:00:00 1990 MSD
isdst=1
> Europe/Kiev  Sat Jun 30 21:59:59 1990 UTC = Sun Jul  1 01:59:59 1990 MSD
isdst=1
> Europe/Kiev  Sat Jun 30 22:00:00 1990 UTC = Sun Jul  1 01:00:00 1990 EEST
isdst=1
> Europe/Kiev  Sat Sep 28 23:59:59 1991 UTC = Sun Sep 29 02:59:59 1991 EEST
isdst=1
> Europe/Kiev  Sun Sep 29 00:00:00 1991 UTC = Sun Sep 29 02:00:00 1991 EET
isdst=0
> ...
>
> Look what happened on July 1, 1990.  At 2 AM, the clocks in Ukraine were
moved back one hour.  So times like 01:30 AM happened twice there on that
day.  Let's see how Python handles this situation
>
> $ TZ=Europe/Kiev python3
> >>> from email.utils import localtime
> >>> from datetime import datetime
> >>> localtime(datetime(1990,7,1,1,30)).strftime('%c %z %Z')
> 'Sun Jul  1 01:30:00 1990 +0400 MSD'
>
> So far so good, I've got the first of the two 01:30AM's.  But what if I
want the other 01:30AM?  Well,
>
> >>> localtime(datetime(1990,7,1,1,30), isdst=0).strftime('%c %z %Z')
> 'Sun Jul  1 01:30:00 1990 +0300 EEST'
>
> gives me "the other 01:30AM", but it is counter-intuitive: I have to ask
for the standard (winter)  time to get the daylight savings (summer) time.
>
> The uncertainty about how to deal with the repeated hour was the reason
why email.utils.localtime-like  interface did not make it to the datetime
module.
>
> The main objection to the isdst flag was that in most situations,
determining whether DST is in effect is as hard as finding the UTC offset,
so reducing the problem of finding the UTC offset to the one of finding the
value for isdst does not solve much.
>
> I now realize that the problem is simply in the name for the flag.  While
we cannot often tell what isdst should be and in some situations the actual
DST status does not differentiate between the two possible times, we can
always say whether we want to get the first or the second time.
>
> In other words, instead of localtime(dt, isdst=-1), we may want
 localtime(dt, which=0) where "which" is used to resolve the ambiguity:
"which=0" means return the first (in UTC order) of the two times and
"which=1" means return the second.  (In the non-ambiguous cases "which" is
ignored.)

It actually took me a long time to understand that the "isdst" flag in this
context related to the following chain of reasoning:

1. Due to various reasons, local time offsets relative to UTC may change,
thus repeating certain subsets of local time
2. Repeated local times usually relate to winding clocks back an hour at
the end of a DST period
3. "isdst=True" thus refers to "before the local time change winds the
clocks back", while "isdst=False" refers to *after* the clocks are wound
back

As Alexander says, you can reduce the amount of assumed knowledge needed to
understand the API by focusing on the ambiguity resolution directly without
assuming that the *reason* for the ambiguity is "end of DST period".

>From a pedagogical point of view, having a separate API that returned 0, 1,
or 2 results for a local time lookup could thus help make it clear that
local time to absolute time conversions are effectively a database lookup
problem, and that timezone offset changes (whether historical or cyclical)
mean that the mapping isn't 1:1 - some expressible local times never
actually happen, while others happen more than once.

For the normal APIs, NonExistentTimeError would then correspond with the
case where the record lookup API returned no results, while the suggested
"which" index would handle the two results case without assuming the
repeated local time was specifically due to the end of a DST period.

Regards,
Nick.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20150410/e9f371ff/attachment.html>


More information about the Python-Dev mailing list