....
[Tim]
>>>> [explains that strict/timeline arithmetic _is_ currently done when
>>>> subtracting or comparing two aware datetimes]
[ijs]
> But only when the aware datetimes have different time zones!
Oh, fudge - yes. My mistake! I overlooked that in my explanation.
And, to be clear, two datetime objects x and y "have the same
timezone" in _this_ context means:
x.tzinfo is y.tzinfo
That is, iff they share a common tzinfo object. In that case they are
indeed subtracted (ditto compared) using classic arithmetic Given
that classic arithmetic is used whenever it can be partly justified in
under 4 pages of dense text, there's simply not a compelling reason to
break from the naive-time model in that case.
So I take back everything I said about your specific example:
> tz = timezone('America/Chicago')
> datetime(2013, 11, 3, 1, 30, tzinfo=tz, is_dst=True) - datetime(2013, 11, 3, 1, 0, tzinfo=tz, is_dst=False)
Assuming this isn't some tzinfo implementation that, e.g., sometimes
magically replaces instances bound to datetimes, then both datetimes
share a common tzinfo member, the result won't change. There are
other cases that _will_ change, when the tzinfo members are different
objects. If you want code similar to this that does give a timeline
arithmetic result, you'll need to use whatever spelling of "use
timeline arithmetic" is implemented (if any).
> It's precisely because of this behavior that pytz has one fixed-offset
> DstTzInfo object for each offset attained over a time zone's history,
> so that two datetimes in the same time zone but with different offsets
> will have different tzinfo members and therefore be converted to UTC
> before being subtracted.
Both painful and clever. While I don't personally care about timezone
transition anomalies (I've never written an app that cared, nor expect
I ever will), if I _did_ I think it's bloody obvious that "the right
thing" to do in Python's current state is to stick to UTC for every
operation apart from conversions for input or output. Classic
arithmetic and timeline arithmetic give the same results in UTC (or
any other eternally-fixed-offset timezone).
Insert "Doctor! Doctor! It hurts when I do this!" "So stop doing it" ;-)
> This also means that addition leaves the datetime with the same fixed
> offset DstTzInfo object, so that calls to .utcoffset() and .dst() return the
> wrong result until the datetime object is normalized, which can be
> accomplished either with tz.normalize(dt) or with dt.astimezone(tz)
>, except that the second way doesn't work if tz is dt.tzinfo (as opposed
> to one of the other DstTzInfo objects corresponding to tz).
I won't do the "Doctor! Doctor" joke more than once in a single message ;-)
> So anyone who is already using pytz is using timedeltas as
> durations anyway, it's just that they and Stuart Bishop have
> to jump through hoops to accomplish it. Which, come to think
> of it, might mean that the backward compatibility problem is
> overrated-- the behavior could be changed for aware datetimes
> only, and .normalize() could be added as a no-op.
I believe Stuart also strongly recommends sticking to UTC (at least
for people who do care about transitions). I conclude he went to all
the trouble primarily because he found it to be an irresistibly
bizarre technical puzzle. I'm only surprised that attitude is
apparently contagious ;-)
> ...
> I'm not clear on how narrowly you mean "this case"
Since I retracted what I said, doesn't matter.
> What about
>
> tz = timezone('America/Chicago')
> dt = datetime(2013, 11, 3, tzinfo=tz)
> [[(dt.astimezone(timezone.utc) + timedelta(hours=n1)).astimezone(tz)
> - dt.astimezone(timezone.utc) + timedelta(hours=n2)).astimezone(tz) for n2 in range(5)] for n1 in range(5)]
>
> ?
I think the project would be better served if you directed this
ingenuity and energy into creating unit tests, or at least a
persistent document that grows over time, or a wiki entry ... What do
_you_ think should happen? I agree that getting edge cases right is
important, but since I strongly doubt I'll be working on them it's of
little real use to invite me to think about them. At heart, they're
just not interesting to me - and mailing lists are black holes. After
I finish answering, this will all be lost in time ;-)
Briefly as I can, because nobody yet knows what will and won't be
implemented, or when each piece that is implemented will land:
- Conversions have always done the best job they could in all cases;
conversions are _outside_ the "naive time" model.
- But conversions inherit utcoffset()'s limitations. Provided
utcoffset's current limitations are repaired, conversion should do the
correct thing in all cases. It cannot today.
- Since you start in Chicago standard time, I expect that (assuming
it's implemented) some spelling.of is_dst will record "no, it's not
DST" in your dt object. That will never change across the duration of
the program. There is no spelling of is_dst today.
- The two instances of `dt.astimezone(timezone.utc)` could be hoisted
out of the nested loops and computed just once: it's a loop
invariant. That value should already be computed correctly (it's
converting a not-near-an-edge-case Chicago daylight time to UTC,
right? easy).
- And there doesn't appear to be anything tricky about the two
additions.. Their left-hand sides are the same loop invariant, and
then a varying number of hours are added to that invariant. Finally,
the additions are performed _in_ UTC, where classic and timeline
arithmetic produce the same results. Nothing changes, and all that
should also already be done correctly
- You've contrived for the additions to create two UTC time spellings
that will convert to the same time spelling in America/Chicago (the
"repeated hour" at the end of Chicago DST). Both now and regardless
of what may change, both such UTC spellings will convert to 1:00:00
Chicago time, via astimezone(tz).
- The top-level subtraction will continue to use classic arithmetic
because both operands share the same tzindo object. Unless some
spelling of "use timeline arithmetic all the time" is implemented
that's somehow triggered by code outside the fragment shown.
So I don't expect any change to the _displayed_ results here. _If_
the top-level subtraction were done with timeline arithmetic instead,
then I would expect changes, provided an .is_dst equivalent is
implemented. In that case, some astimezone(tz) intermediate results
would change, not in the Chicago HH:MM:SS results delivered, but that
some of the Chicago 01:00:00 results would have is_dst False and
others True. Classic arithmetic won't even see that, but timeline
subtraction would take those differences into account.
Enough? Did I even stumble into the issue you were _really_ wondering
about? ;-)
> ...
> ...I might have had some comments of that nature in my code until fairly recently. Though they
> weren't as angry as the comments about getting pytz and dateutil.tz to interoperate.
Hear about the guy who went to the doctor and complained "Doctor!
Doctor! It hurts when I do this!"? Na, me neither ;-)
> Or the comments about the device that tried to store sub-second resolution data in
> Excel format (days since 1899-12-30)... to five decimal places.
I _am_ an expert on floating-point trivia. In case you were
wondering, the person who wrote the software for that device was ...
optimistic ;-)