[Zope3-dev] Re: [Python-Dev] Holes in time

Tim Peters tim@zope.com
Wed, 1 Jan 2003 23:13:05 -0500


[Guido, on the DST start case:  in US Eastern, 2:MM doesn't exist
 on the wall clock, which jumps from 1:MM to 3:MM; the example
 tzinfo classes take 2:MM as being daylight then, since it's
 "after 2", and astimezone() delivers the equivalent standard-time
 1:MM spelling when it has to deliver a result in this hour]

> I don't particularly care one way or the other, but when I'm *awake*
> during this hour, 2:MM more likely means that I forgot to move my
> clock forward, so it may make more sense to interpret it as standard
> time after all (meaning it should be switched to 3:MM DST rather than
> 1:MM STD).

Note that this issue is almost entirely about how users code *their* tzinfo
classes:  the interpretion of 2:MM is up to them.  If you would like to call
2:MM standard time instead, then write your dst() method accordingly.  Note
that the example time zone classes in US.py call 2:MM daylight time "because
it's after 2:00", and the time zone classes in EU.py do an equivalent thing
for the time zones they implement; it's the most natural thing to code.  If
this hour happens to be the result of an astimezone() conversion, it picks
the unambiguous standard-time spelling (which is 1:MM whether you moved your
clock forward at exactly the right moment, or waited until the next day; the
astimezone() part of this case is simply that it doesn't deliver an
ambiguous result in this case -- an Eastern tzinfo subclass would be insane
if it called 1:MM daylight time on this day).

[on the unspellable hour at the end of DST]
> One (perhaps feeble) argument against raising ValueError here is that
> this introduces a case where a calculation that normally never raises
> an error (assuming sane timezones) can raise an exception for one hour
> a year.  If you run a webserver that e.g. tries to render the current
> time at the server (which is represented in UTC of course) in the end
> user's local time, it would be embarrassing if this caused an error
> page when the end user's local time happens to be in the
> unrepresentable hour (i.e. one hour per year).  You really want to
> render it as 1:MM, since the user should know whether his DST has
> already ended or not yet.  While in an ideal world the programmer
> would have read the docs for .astimezone() and heeded the warning to
> catch ValueError (and then display what? UTC?),

The parenthetical questions have to be answered even if the implementation
changes:  if astimezone() doesn't raise an exception here, it has to set
*some* time values in the result object, and the server will display them.
Probably 1:MM in the Eastern case -- it would take some thought to figure
out whether it could at least promise to deliver the ambiguous wall-clock
spelling in this case (so that it's never worse than an off-by-one (hour)
error).

> realistically if the programmer is a mere mortal and found no
> problems during testing, she will be embarrassed by the error page.
> (For worse effect, imagine the server running in a space probe :-)
>
> The counterargument is of course a use case where the time displayed
> is of real importance, and we would rather die than show the wrong
> time.

Displaying times across time zones is a pretty harmless case.  The reason I
worry more about this here is that one-zone (intrazone, as opposed to your
interzone use case) DST-aware arithmetic can't be done in the datetime
module without converting to UTC (or some other fixed reference), doing the
arithmetic there, and converting back again.  In that set of use cases,
getting an incorrect result seems much more likely to have bad consequences.
If the Russian space probe is programmed in some hybrid Russian std+daylight
time zone, you tell it to turn away from the sun 6 hours from now, and it
doesn't actually turn away for 7 hours and fries itself as a result, it will
be a lead story on comp.risks.  Although I hope they have sense enough to
pick on it more for programming a space probe in some hybrid Russian
std+daylight time zone <wink>.

Perhaps astimezone() could grow an optional "error return" value, a
datetimetz to be used if the time is unrepresentable in the target zone.

> The C standard library (following the original Unix treatment of
> timezones) has a solution for this: it adds a three-valued flag to the
> local time which indicates whether DST is in effect or not.  Normally,
> you can set this flag to -1 ("don't know") in which case the proper
> value is calculated from the DST rules.  But for ambiguous local times
> (the hour at the end of DST), setting it to 0 or 1 makes the times
> unambiguous again.  This would mean that we'd have to add an "isdst"
> field to datetimetz objects and ways to set it; it would default to -1
> (or perhaps to the proper value based on DST rules) but .astimezone()
> could set it explicitly to 0 or 1 to differentiate between the two
> ambiguous times.

This could work too.

>> 3. If dt.tzinfo.dst(dt) returns None, the current implementation
>> takes that as a synonym for 0.  Perhaps it should raise an exception
>> instead.

> Why would dst() return None for a tzinfo object whose utcoffset()
> returns a definite value?  I think that's a poor tzinfo implementation
> and an exception would be appropriate.

Only because the first three times I wrote a tzinfo class, I didn't give a
rip about DST so implemented dst() to return None <wink>.  I'm afraid you
still have to care *enough* to read the docs, and heed the advice that dst()
should return 0 if DST isn't in effect.  I'll change this.

>> 4. If dt.tzinfo.utcoffset(dt) first returns an offset, and on a
>> subsequent call (while still trying to figure out the same
>> conversion) returns None, an exception is raised.  Those who don't
>> want unspellable hours to raise an exception may also want
>> inconsistent tzinfo implementations to go without complaint.  If so,
>> what do you want it to do instead?

> I think this could only happen if a tzinfo's utcoffset() returns None
> for *some* times but not for others, right?

Correct.  When this happens, the implementation currently raises ValueError,
with a msg complaining that the utcoffset() method is inconsistent.

> I don't think such a tzinfo should be considered sane.

Then you don't hate the current implementation on this count <wink>.

> Hmm, perhaps that's how a tzinfo object would signal that a particular
> local time is illegal (e.g. the hour at the start of DST).

I'm not worried about that case:  astimezone() never *produces* that
spelling (not even internally -- see the long new correctness proof for the
gory details).

> Then astimezone() would have to worm around that in some other way.

If the input hour to astimezone() is of that ambiguous form, astimezone()
already takes a None return from utcoffset() as meaning the input datetimetz
is naive, and simply attaches the new timezone to the input date and time
fields without altering the latter.

On the other end (when DST ends), since there's no way to spell the
"impossible hour" in local time, a tzinfo class knows nothing about that
hour (in particular, it can't detect it -- astimezone() can, based on the
otherwise impossible sequence of results it gets from calling utcoffset()
more than once).