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

Akira Li 4kir4.1i at gmail.com
Sat Apr 18 02:19:41 CEST 2015


On Thu, Apr 16, 2015 at 1:14 AM, Alexander Belopolsky <
alexander.belopolsky at gmail.com> wrote:

>
> On Wed, Apr 15, 2015 at 4:46 PM, Akira Li <4kir4.1i at gmail.com> wrote:
>
>> > 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.
>> >
>>
>> It looks incorrect. Here's the corresponding pytz code:
>>
>>   from datetime import datetime
>>   import pytz
>>
>>   tz = pytz.timezone('Europe/Kiev')
>>   print(tz.localize(datetime(1990, 7, 1, 1, 30),
>> is_dst=False).strftime('%c %z %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0300 EEST
>>   print(tz.localize(datetime(1990, 7, 1, 1, 30),
>> is_dst=True).strftime('%c %z %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0400 MSD
>>
>> See also "Enhance support for end-of-DST-like ambiguous time" [1]
>>
>> [1] https://bugs.launchpad.net/pytz/+bug/1378150
>>
>> `email.utils.localtime()` is broken:
>>
>
> If you think there is a bug in email.utils.localtime - please open an
> issue at <bugs.python.org>.
>
>

Your question below suggests that you believe it is not a bug i.e.,
`email.utils.localtime()` is broken *by design* unless you think it is ok
to ignore `+0400 MSD`.

pytz works for me (I can get both `+0300 EEST` and `+0400 MSD`).  I don't
think `localtime()` can be fixed without the tz database. I don't know
whether it should be fixed, let somebody else who can't use pytz to pioneer
the issue. The purpose of the code example is to **inform** that
`email.utils.localtime()` fails (it returns only +0300 EEST) in this case:


>>   from datetime import datetime
>>   from email.utils import localtime
>>
>>   print(localtime(datetime(1990, 7, 1, 1, 30)).strftime('%c %z %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0300 EEST
>>   print(localtime(datetime(1990, 7, 1, 1, 30), isdst=0).strftime('%c %z
>> %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0300 EEST
>>   print(localtime(datetime(1990, 7, 1, 1, 30), isdst=1).strftime('%c %z
>> %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0300 EEST
>>   print(localtime(datetime(1990, 7, 1, 1, 30), isdst=-1).strftime('%c %z
>> %Z'))
>>   # -> Sun Jul  1 01:30:00 1990 +0300 EEST
>>
>>
>> Versions:
>>
>>   $ ./python -V
>>   Python 3.5.0a3+
>>   $ dpkg -s tzdata | grep -i version
>>   Version: 2015b-0ubuntu0.14.04
>>
>> > 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.
>>
>> "repeated hour" (time jumps back) can be treated like a end-of-DST
>> transition, to resolve ambiguities [1].
>
>
> I don't understand what you are complaining about.  It is quite possible
> that pytz uses is_dst flag differently from the way email.utils.localtime
> uses isdst.
>
> I was not able to find a good description of what is_dst means in pytz,
> but localtime's isdst is documented as follows:
>
>     a positive or zero value for *isdst* causes localtime to
>     presume initially that summer time (for example, Daylight Saving Time)
>     is or is not (respectively) in effect for the specified time.
>
> Can you demonstrate that email.utils.localtime does not behave as
> documented?
>


No need to be so defensive about it. *""repeated hour" (time jumps back)
can be treated like a end-of-DST transition, to resolve ambiguities [1]."*
is just a *an example* on how to fix the problem in the same way how it is
done in pytz:

  >>> from datetime import datetime
  >>> import pytz
  >>> tz = pytz.timezone('Europe/Kiev')
  >>> after = tz.localize(datetime(1990, 7, 1, 1, 30), is_dst=False)
  >>> before = tz.localize(datetime(1990, 7, 1, 1, 30), is_dst=True)
  >>> before < after
  True
  >>> before
  datetime.datetime(1990, 7, 1, 1, 30, tzinfo=<DstTzInfo 'Europe/Kiev'
MSD+4:00:00 DST>)
  >>> after
  datetime.datetime(1990, 7, 1, 1, 30, tzinfo=<DstTzInfo 'Europe/Kiev'
EEST+3:00:00 DST>)
  >>> before.astimezone(pytz.utc)
datetime.datetime(1990, 6, 30, 21, 30, tzinfo=<UTC>)
  >>> after.astimezone(pytz.utc)
datetime.datetime(1990, 6, 30, 22, 30, tzinfo=<UTC>)
  >>> before.dst()
datetime.timedelta(0, 3600)
  >>> after.dst()
datetime.timedelta(0, 3600)
  >>> pytz.OLSON_VERSION
  '2015b'

Here's "summer time" in both cases i.e., it is not *true* end-of-DST
transition (that is why I've used the word *"like"* above).

If we ignore ambiguous time that may occur more than twice then a boolean
flag such as pytz's is_dst is *always* enough to resolve the ambiguity
assuming we have access to the tz database.

And yes, the example demonstrates that the behavior of pytz's is_dst and
localtime()'s isdst is different. The example just shows that the current
behavior of localtime() doesn't allow to get `+0400 DST` (on my system, see
the software versions above) and how to get it (*adopt* the pytz behavior
-- you need zoneinfo for that) i.e., the message is a problem and a
possible solution -- no complains.

[1] https://bugs.launchpad.net/pytz/+bug/1378150


--
Akira.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20150418/cc3ac706/attachment.html>


More information about the Python-Dev mailing list