[Datetime-SIG] Are there any "correct" implementations of tzinfo?

Tim Peters tim.peters at gmail.com
Mon Sep 14 20:53:33 CEST 2015

>> pytz solves it by _never_ creating a hybrid tzinfo.  It only uses
>> eternally-fixed-offset tzinfos.  For example, for a conceptual zone
>> with two possible total UTC offsets (one for "daylight", one for
>> "standard"), there two distinct eternally-fixed-offset tzinfo objects
>> in pytz.  Then an ambiguous time is resolved by _which_ specific
>> tzinfo object is attached.  Typically the "daylight" tzinfo for the
>> first time a repeated local time appears, and the "standard" tzinfo
>> for its second appearance

[Laura Creighton <lac at openend.se>]
> Yes.  I think this is a really great idea.  I have no idea why other
> people disagree.


>> In return, you have to use .localize() and .normalize() at various
>> times, because pytz's tzinfo objects themselves are completely blind
>> to the possibility of the total UTC offset changing. .localize() and
>> .normalize() are needed to possibly _replace_ the tzinfo object in
>> use, depending on the then-current date and time.

> Yes.

>>OTOH, `dateutil` does create hybrid tzinfo objects.  No dances are
>>ever needed to possibly replace them.  But it's impossible for
>>dateutil's tzinfos to disambiguate times in a fold.  Incidentally,
>>dateutil also makes no attempt to account for transitions other than
>>DST (e.g., sometimes a zone may change its _base_ ("standard") offset
>>from UTC).

> I find this totally unacceptable.

The precise referent for "this" isn't clear to me.  The lack of
knowledge of base-offset transitions is a property of dateutil's
implementation, due to inheriting the default .fromutc() instead of
implementing its own to take advantage of all the transition info a
tzfile records.  It's not _inherent_ to hybrid tzinfos.  Just an
omission in this specific zoneinfo wrapping.

> My conclusion was that hybrid tzinfo
> objects were a _really stupid idea_ proposed by somebody who misunderstood
> the problem, or rather only understood the most common case.  Smack them
> with a dead fish,  https://www.youtube.com/watch?v=i9SSOWORzw4
> and get back to work.

So, on your own machine, whenever daylight time starts or ends, you
manually change your TZ environment variable to specify the newly
appropriate eternally-fixed-offset zone?  Of course not.  Your OS
doesn't change it for you by magic either.  Your OS implements a
hybrid tzinfo:   it knows all by itself what the appropriate current
total UTC offset is, by consulting the tzfile (or hybrid POSIX TZ
rule) you attached to your account the last time you set it, however
many years ago that may have been.  Indeed, I don't know of any
software package _other_ than pytz doing it the "switch
eternally-fixed-offset zones" way.  Not that it's impossible Stuart is
the first person in history not to implement it in a "really stupid"
way ;-)  But it was more that Stuart made heroic efforts to leap the
datetime design gap explained next:

In all POSIX-ish OSes, hybrid tzinfos are just business as usual:  a
struct tm's tm_isdst flag distinguishes ambiguous times.  localtime()
(UTC->local) sets tm_isdst by magic for you, and mktime() (local->UTC)
consults tm_isdst to disambiguate local times in a fold.

A datetime object is the Python spelling of a C struct tm, but never
included the tm_isdst flag.  PEP 495 aims to supply a similar bit.
When it does, hybrid tzinfos will do cross-zone conversions correctly
in all cases.  Before then, pytz uses eternally-fixed-offset zones
primarily to overcome the current lack of that bit.

C's localtime() and mktime() are both spelled .astimezone(...) in
Python, but under the covers localtime() is essentially Python's
fromutc() (which will set the new bit appropriately after 495) and
mktime() is essentially .utcoffset() (which will consult the new bit
after 495).

That's all there is to it.  "In theory" ;-)  datetime will become
more-or-less exactly as stupid as POSIX has been for some decades.

>> So, yup, if you're thoroughly indoctrinated in pytz behavior, you will
>> be accurate but appear insane to Guido ;-)  At a semantic level, a
>> pytz tzinfo doesn't capture the notion of a zone with offset changes -
>> it doesn't even try to.  All knowledge about offset changes is inside
>> the .localize() and .normalize() dances.

> I can see why people would like to modify it to spit out this information
> when asked.  I don't understand why they would like to have a hybrid
> tzinfo.  The notion of replacing tzinfos when they become inappropriate
> fills their souls with revulsion, or something?

Because, e.g., a hybrid tzinfo (after 495) _can_ do it correctly all
the time, with no need for user interventions ever.  Same as your OS
does.  That's about usability and lack of surprise.  It didn't take
much web cruising, e.g., to find baffled new pytz users wondering why
the zone displayed "was wrong" sometimes in utterly unexceptional
cases (nowhere near a fold or a gap).  "Read the docs!  normalize()!"
Why should they _have_ to?  The only other things in their life that
demand they manually fiddle to adjust for UTC-offset transitions are
"dumb clocks", where _needing_ to fiddle is universally viewed as a
twice-a-year annoyance.

> But, as I said, once you know the pytz way you may be ruined for
> appreciating other solutions.

Well, take just a moment to appreciate that your OS does use hybrid
tzinfos, and that it works so smoothly you never really noticed it ;-)

More information about the Python-list mailing list