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

Guido van Rossum guido@python.org
Wed, 01 Jan 2003 22:20:05 -0500


> 1. When DST starts (the "wall start" line), the wall clock leaps from 1:59
> to 3:00.  A wall time of the form 2:MM doesn't really make sense on
> that day.  The example classes do what I believe is the best that
> can be done: since 2:MM is "after 2" on that day, it's taken as
> daylight time, and so as an alias for 1:MM standard == 1:MM wall on
> that day, which is a time that does make sense on the wall clock
> that day.  The astimezone() function ensures that the "impossible
> hour" on that day is never the result of a conversion (you'll get
> the standard-time spelling instead).  If you don't think that's the
> best that can be done, speak up now.

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).

> 2. When DST ends (the "wall end" line), we have a worse problem:
> there'a an hour that can't be spelled *at all* in wall time.  It's
> the hour beginning at the moment DST ends; in the example, that's
> times of the form 6:MM UTC on the day daylight time ends.  The local
> wall clock leaps from 1:59 (daylight time) back to 1:00 again (but
> the second time as a standard time).  The hour 6:MM UTC looks like
> 1:MM, but so does the hour 5:MM UTC on that day.  A reasonable
> tzinfo class should take 1:MM as being daylight time on that day,
> since it's "before 2".  As a consequence, the hour 6:MM UTC has no
> wall-clock spelling at all.
> 
> This can't be glossed over.  If you code a tzinfo class to take 1:MM
> as being standard time on that day instead, then the UTC hour 5:MM
> becomes unspellable in wall time instead.  No matter how you cut it,
> the redundant spellings of an hour on the day DST starts means
> there's an hour that can't be spelled at all on the day DST ends (so
> in that sense, they're the two sides of a single problem).
> 
> What to do?  The current implementation of dt.astimezone(tz) raises
> ValueError if dt can't be expressed as a local time in tz.  That's
> the "errors should never pass silently" school, which I briefly
> attended in college <wink>.  If you don't like that, what would you
> rather see happen?  Try to be precise, and remember that getting a
> "correct" time in tz is flatly impossible in this case.

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?), 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.

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.

> Two other debatable edge cases in the implementation of
> dt.astimezone(tz):
> 
> 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.

> 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?  I don't think such a
tzinfo should be considered sane.  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).  Then astimezone() would have to worm
around that in some other way.

--Guido van Rossum (home page: http://www.python.org/~guido/)