[Datetime-SIG] Conversion vs arithmetic (was Re: Is EDT a timezone? Was: PEP-0500)

Tim Peters tim.peters at gmail.com
Mon Aug 24 20:03:44 CEST 2015


Sorry, I can only make time for a single point now - but it's an
important one, and seemingly subtle ;-)

[Stuart Bishop <stuart at stuartbishop.net>]
> ...
> Conversion was the larger issue, and to do that correctly the
> arithmetic needed to be fixed.

Let's be really clear on this:  conversion has nothing to do with
arithmetic.  Well, yes, it does:  doing conversion correctly is
easiest using _classic_ arithmetic, which we already have.

By "conversion" I mean conversion:  .astimezone() and .fromutc().  You
gave an example in another message today you called "conversion" that
actually mixed conversion with addition, and that's not what I mean by
"conversion".  By "conversion", I only mean the conversion part of
that example ;-)

The problems with conversion have entirely to do with that .fromutc()
today is incapable of setting a bit to say which UTC value was
intended when the result in the destination zone is ambiguous, which
in turn means .utcoffset() in the destination zone has no way to know
either.  That, and only that, is what makes conversions fail in some
cases today.

When tzinfos implementing PEP 495 are available, all conversions using
those tzinfos will be fixed "by magic".  Achieving that requires no
change of any kind to arithmetic.  To the contrary, this is all
.astimezone(self, tz) does (in non-degenerate cases):

        myoffset = self.utcoffset()
        utc = (self - myoffset).replace(tzinfo=tz) # convert to UTC
and paste on tz
        return tz.fromutc(utc)

The subtraction (in the 2nd line) must use classic arithmetic; if it
used timeline arithmetic instead, it could at best fall into an
infinite regress (the line is _implementing_ conversion of `self` to
UTC, but in timeline arithmetic subtraction would first try to convert
`self` to UTC, which in turn ...  Lennart bumped into this in various
guises, which is why his PEP's implementation stalled - implementing
"timeline arithmetic always" doesn't _help_ conversion, it gets in the
way - classic arithmetic is the rock that stops it from being "turtles
all the way down" ;-) ).

No change to .astimezone() is needed either.  Getting conversions
right is entirely about:

1. .The utcoffset() in line 1 returning the correct result, which in
turn is entirely about .utcoffset() knowing which value to return when
`self` is in a fold.  PEP 495 is enough to address that.

and

2. .fromutc() setting `fold` correctly in the final (`return
tz.fromutc(utc)`) line.  PEP 495 is enough to address that too.

Timeline arithmetic is a different issue.  `fold` is necessary but not
sufficient to get by-magic timeline arithmetic; `fold` is both
necessary and sufficient to repair conversions.

If and when optional timeline arithmetic is implemented, _then_ .the
.astimezone() implementation will need to change, to _force_ use of
classic arithmetic to convert to UTC.  You should consider that to be
an example of crucial code that indeed relies on classic arithmetic.

You haven't bumped into anything like that in pytz because pytz did
not change arithmetic:  to "get the effect" of timeline arithmetic,
users have to explicitly invoke a distinct .normalize() method in
pytz.  Nothing (whether in Python, other user code, 3rd-party
libraries ...) relying on classic arithmetic _could_ be affected by
that, so of course you never saw any problems.

Lennart fell into a bottomless pit of pain when he did try changing
default arithmetic.  Which is why the default cannot be changed:
Lennart already showed that changing it creates a bottomless pit of
pain; indeed, it was so deep he never managed to climb out of it :-(


More information about the Datetime-SIG mailing list