"Premature" doc changes, for new astimezone() rules, and the new

tzinfo.fromutc() method.  The C code doesn't implement any of this
yet (well, not the C code on the machine I'm using now), nor does
the test suite reflect it.  The Python datetime.py implementation and
test suite in the sandbox do match these doc changes.  The C
implementation probably won't catch up before Thursday (Wednesday is
a scheduled "black hole" day this week <0.4 wink>).
This commit is contained in:
Tim Peters 2003-01-22 04:45:50 +00:00
parent 51f3f1b7dc
commit f196a0a4dd
3 changed files with 180 additions and 60 deletions

View File

@ -734,32 +734,71 @@ Instance methods:
\begin{methoddesc}{astimezone}{tz}
Return a \class{datetime} object with new \member{tzinfo} member
\var{tz}.
\var{tz} must be \code{None}, or an instance of a \class{tzinfo} subclass.
If \var{tz} is \code{None}, \var{self} is naive,
or \code{self.tzinfo}\ is \var{tz},
\code{self.astimezone(tz)} is equivalent to
\code{self.replace(tzinfo=tz)}: a new time zone object is attached
without any conversion of date or time members. Else \code{self.tzinfo}
and \var{tz} must implement the \method{utcoffset()} and \method{dst()}
\class{tzinfo} methods, and the date and time members are adjusted so
that the result is local time in time zone \var{tz}, representing the
same UTC time as \var{self}: after \code{astz = dt.astimezone(tz)},
\code{astz - astz.utcoffset()} will usually have the same date and time
members as \code{dt - dt.utcoffset()}. The discussion of class
\class{tzinfo} explains the cases at Daylight Saving Time
transition boundaries where this cannot be achieved (an issue only if
\var{tz} models both standard and daylight time).
\var{tz}, adjusting the date and time members so the result is the
same UTC time as \var{self}, but in \var{tz}'s local time.
\var{tz} must be an instance of a \class{tzinfo} subclass, and its
\method{utcoffset()} and \method{dst()} methods must not return
\code{None}. \var{self} must be aware (\code{\var{self}.tzinfo} must
not be \code{None}, and \code{\var{self}.utcoffset()} must not return
\code{None}).
If code{\var{self}.tzinfo} is \var{tz},
\code{\var{self}.astimezone(\var{tz})} is equal to \var{self}: no
adjustment of date or time members is performed.
Else the result is local time in time zone \var{tz}, representing the
same UTC time as \var{self}: after \code{\var{astz} =
\var{dt}.astimezone(\var{tz})},
\code{\var{astz} - \var{astz}.utcoffset()} will usually have the same
date and time members as \code{\var{dt} - \var{dt}.utcoffset()}.
The discussion of class \class{tzinfo} explains the cases at Daylight
Saving Time transition boundaries where this cannot be achieved (an issue
only if \var{tz} models both standard and daylight time).
If you merely want to attach a time zone object \var{tz} to a
datetime \var{dt} without adjustment of date and time members,
use \code{\var{dt}.replace(tzinfo=\var{tz})}. If
you merely want to remove the time zone object from an aware datetime
\var{dt} without conversion of date and time members, use
\code{\var{dt}.replace(tzinfo=None)}.
Note that the default \method{tzinfo.fromutc()} method can be overridden
in a \class{tzinfo} subclass to effect the result returned by
\method{astimezone()}. Ignoring error cases, \method{astimezone()}
acts like:
\begin{verbatim}
def astimezone(self, tz):
if self.tzinfo is tz:
return self
# Convert self to UTC, and attach the new time zone object.
utc = (self - self.utcoffset()).replace(tzinfo=tz)
# Convert from UTC to tz's local time.
return tz.fromutc(utc)
\end{verbatim}
\end{methoddesc}
\begin{methoddesc}{utcoffset}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{tzinfo.utcoffset(self)}.
returns \code{\var{self}.tzinfo.utcoffset(\var{self})}, and
raises an exception if the latter doesn't return \code{None}, or
a \class{timedelta} object representing a whole number of minutes
with magnitude less than one day.
\end{methoddesc}
\begin{methoddesc}{dst}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{\var{self}.tzinfo.dst(\var{self})}, and
raises an exception if the latter doesn't return \code{None}, or
a \class{timedelta} object representing a whole number of minutes
with magnitude less than one day.
\end{methoddesc}
\begin{methoddesc}{tzname}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{tzinfo.tzname(self)}.
returns \code{\var{self}.tzinfo.tzname(\var{self})},
raises an exception if the latter doesn't return \code{None} or
a string object,
\end{methoddesc}
\begin{methoddesc}{timetuple}{}
@ -989,17 +1028,25 @@ Instance methods:
\begin{methoddesc}{utcoffset}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{tzinfo.utcoffset(self)}.
returns \code{\var{self}.tzinfo.utcoffset(None)}, and
raises an exception if the latter doesn't return \code{None} or
a \class{timedelta} object representing a whole number of minutes
with magnitude less than one day.
\end{methoddesc}
\begin{methoddesc}{dst}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{tzinfo.dst(self)}.
returns \code{\var{self}.tzinfo.dst(None)}, and
raises an exception if the latter doesn't return \code{None}, or
a \class{timedelta} object representing a whole number of minutes
with magnitude less than one day.
\end{methoddesc}
\begin{methoddesc}{tzname}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else
returns \code{tzinfo.tzname(self)}.
returns \code{\var{self}.tzinfo.tzname(None)}, or
raises an exception if the latter doesn't return \code{None} or
a string object.
\end{methoddesc}
@ -1066,37 +1113,58 @@ implement all of them.
example, \method{datetime.timetuple()} calls its \member{tzinfo}
member's \method{dst()} method to determine how the
\member{tm_isdst} flag should be set, and
\method{datetime.astimezone()} calls \method{dst()} to account for
\method{tzinfo.fromutc()} calls \method{dst()} to account for
DST changes when crossing time zones.
An instance \var{tz} of a \class{tzinfo} subclass that models both
standard and daylight times must be consistent in this sense:
\code{tz.utcoffset(dt) - tz.dst(dt)}
\code{\var{tz}.utcoffset(\var{dt}) - \var{tz}.dst(\var{dt})}
must return the same result for every \class{datetime} \var{dt}
with \code{dt.tzinfo==tz} For sane \class{tzinfo} subclasses, this
expression yields the time zone's "standard offset", which should not
depend on the date or the time, but only on geographic location. The
implementation of \method{datetime.astimezone()} relies on this, but
cannot detect violations; it's the programmer's responsibility to
ensure it.
with \code{\var{dt}.tzinfo==\var{tz}} For sane \class{tzinfo}
subclasses, this expression yields the time zone's "standard offset",
which should not depend on the date or the time, but only on geographic
location. The implementation of \method{datetime.astimezone()} relies
on this, but cannot detect violations; it's the programmer's
responsibility to ensure it. If a \class{tzinfo} subclass cannot
guarantee this, it may be able to override the default implementation
of \method{tzinfo.fromutc()} to work correctly with \method{astimezone()}
regardless.
Most implementations of \method{dst()} will probably look like one
of these two:
\begin{verbatim}
return timedelta(0) # a fixed-offset class: doesn't account for DST
or
# Code to set dston and dstoff to the time zone's DST transition
# times based on the input dt.year, and expressed in standard local
# time. Then
if dston <= dt.replace(tzinfo=None) < dstoff:
return timedelta(hours=1)
else:
return timedelta(0)
\end{verbatim}
The default implementation of \method{dst()} raises
\exception{NotImplementedError}.
\end{methoddesc}
\begin{methoddesc}{tzname}{self, dt}
Return the timezone name corresponding to the \class{datetime}
object represented
by \var{dt}, as a string. Nothing about string names is defined by the
\module{datetime} module, and there's no requirement that it mean anything
in particular. For example, "GMT", "UTC", "-500", "-5:00", "EDT",
"US/Eastern", "America/New York" are all valid replies. Return
Return the time zone name corresponding to the \class{datetime}
object \var{dt}, as a string.
Nothing about string names is defined by the
\module{datetime} module, and there's no requirement that it mean
anything in particular. For example, "GMT", "UTC", "-500", "-5:00",
"EDT", "US/Eastern", "America/New York" are all valid replies. Return
\code{None} if a string name isn't known. Note that this is a method
rather than a fixed string primarily because some \class{tzinfo} objects
will wish to return different names depending on the specific value
of \var{dt} passed, especially if the \class{tzinfo} class is
rather than a fixed string primarily because some \class{tzinfo}
subclasses will wish to return different names depending on the specific
value of \var{dt} passed, especially if the \class{tzinfo} class is
accounting for daylight time.
The default implementation of \method{tzname()} raises
@ -1113,7 +1181,7 @@ class \class{datetime}.
When \code{None} is passed, it's up to the class designer to decide the
best response. For example, returning \code{None} is appropriate if the
class wishes to say that time objects don't participate in the
\class{tzinfo} protocol. It may be more useful for \code{utcoffset(None)}
\class{tzinfo} protocols. It may be more useful for \code{utcoffset(None)}
to return the standard UTC offset, as there is no other convention for
discovering the standard offset.
@ -1124,6 +1192,50 @@ user code calls \class{tzinfo} methods directly. The intent is that
the \class{tzinfo} methods interpret \var{dt} as being in local time,
and not need worry about objects in other timezones.
There is one more \class{tzinfo} method that a subclass may wish to
override:
\begin{methoddesc}{fromutc}{self, dt}
This is called from the default \class{datetime.astimezone()}
implementation. When called from that, \code{\var{dt}.tzinfo} is
\var{self}, and \var{dt}'s date and time members are to be viewed as
expressing a UTC time. The purpose of \method{fromutc()} is to
adjust the date and time members, returning an equivalent datetime in
\var{self}'s local time.
Most \class{tzinfo} subclasses should be able to inherit the default
\method{fromutc()} implementation without problems. It's strong enough
to handle fixed-offset time zones, and time zones accounting for both
standard and daylight time, and the latter even if the DST transition
times differ in different years. An example of a time zone the default
\method{fromutc()} implementation may not handle correctly in all cases
is one where the standard offset (from UTC) depends on the specific date
and time passed, which can happen for political reasons.
The default implementations of \method{astimezone()} and
\method{fromutc()} may not produce the result you want if the result is
one of the hours straddling the moment the standard offset changes.
Skipping code for error cases, the default \method{fromutc()}
implementation acts like:
\begin{verbatim}
def fromutc(self, dt):
# raise ValueError error if dt.tzinfo is not self
dtoff = dt.utcoffset()
dtdst = dt.dst()
# raise ValueError if dtoff is None or dtdst is None
delta = dtoff - dtdst # this is self's standard offset
if delta:
dt += delta # convert to standard local time
dtdst = dt.dst()
# raise ValueError if dtdst is None
if dtdst:
return dt + dtdst
else:
return dt
\end{verbatim}
\end{methoddesc}
Example \class{tzinfo} classes:
\verbatiminput{tzinfo-examples.py}
@ -1150,7 +1262,7 @@ to 3:00. A wall time of the form 2:MM doesn't really make sense on that
day, so \code{astimezone(Eastern)} won't deliver a result with
\code{hour==2} on the
day DST begins. In order for \method{astimezone()} to make this
guarantee, the \class{tzinfo} \method{dst()} method must consider times
guarantee, the \method{rzinfo.dst()} method must consider times
in the "missing hour" (2:MM for Eastern) to be in daylight time.
When DST ends (the "end" line), there's a potentially worse problem:
@ -1162,8 +1274,8 @@ Local times of the form 1:MM are ambiguous. \method{astimezone()} mimics
the local clock's behavior by mapping two adjacent UTC hours into the
same local hour then. In the Eastern example, UTC times of the form
5:MM and 6:MM both map to 1:MM when converted to Eastern. In order for
\method{astimezone()} to make this guarantee, the \class{tzinfo}
\method{dst()} method must consider times in the "repeated hour" to be in
\method{astimezone()} to make this guarantee, the \method{tzinfo.dst()}
method must consider times in the "repeated hour" to be in
standard time. This is easily arranged, as in the example, by expressing
DST switch times in the time zone's standard local time.
@ -1235,9 +1347,7 @@ Struct typedefs:
PyDateTime_Date
PyDateTime_DateTime
PyDateTime_DateTimeTZ
PyDateTime_Time
PyDateTime_TimeTZ
PyDateTime_Delta
PyDateTime_TZInfo
@ -1249,15 +1359,9 @@ Type-check macros:
PyDateTime_Check(op)
PyDateTime_CheckExact(op)
PyDateTimeTZ_Check(op)
PyDateTimeTZ_CheckExact(op)
PyTime_Check(op)
PyTime_CheckExact(op)
PyTimeTZ_Check(op)
PyTimeTZ_CheckExact(op)
PyDelta_Check(op)
PyDelta_CheckExact(op)
@ -1269,18 +1373,18 @@ Accessor macros:
All objects are immutable, so accessors are read-only. All macros
return ints:
For \class{date}, \class{datetime}, and \class{datetimetz} instances:
For \class{date} and \class{datetime} instances:
PyDateTime_GET_YEAR(o)
PyDateTime_GET_MONTH(o)
PyDateTime_GET_DAY(o)
For \class{datetime} and \class{datetimetz} instances:
For \class{datetime} instances:
PyDateTime_DATE_GET_HOUR(o)
PyDateTime_DATE_GET_MINUTE(o)
PyDateTime_DATE_GET_SECOND(o)
PyDateTime_DATE_GET_MICROSECOND(o)
For \class{time} and \class{timetz} instances:
For \class{time} instances:
PyDateTime_TIME_GET_HOUR(o)
PyDateTime_TIME_GET_MINUTE(o)
PyDateTime_TIME_GET_SECOND(o)

View File

@ -27,7 +27,7 @@ class FixedOffset(tzinfo):
"""Fixed offset in minutes east from UTC."""
def __init__(self, offset, name):
self.__offset = timdelta(minutes = offset)
self.__offset = timedelta(minutes = offset)
self.__name = name
def utcoffset(self, dt):
@ -116,9 +116,9 @@ class USTimeZone(tzinfo):
def dst(self, dt):
if dt is None or dt.tzinfo is None:
# An exception may be sensible here, in one or both cases.
# It depends on how you want to treat them. The astimezone()
# implementation always passes a datetime with
# dt.tzinfo == self.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self

View File

@ -45,7 +45,7 @@ Extension modules
microsecond <http://www.python.org/sf/661086>. This repairs an
irritation most likely seen on Windows systems.
In dt.asdatetime(tz), if tz.utcoffset(dt) returns a duration,
In dt.astimezone(tz), if tz.utcoffset(dt) returns a duration,
ValueError is raised if tz.dst(dt) returns None (2.3a1 treated it
as 0 instead, but a tzinfo subclass wishing to participate in
time zone conversion has to take a stand on whether it supports
@ -60,11 +60,27 @@ Extension modules
The example tzinfo class for local time had a bug. It was replaced
by a later example coded by Guido.
datetimetz.astimezone(tz) no longer raises an exception when the
datetime.astimezone(tz) no longer raises an exception when the
input datetime has no UTC equivalent in tz. For typical "hybrid" time
zones (a single tzinfo subclass modeling both standard and daylight
time), this case can arise one hour per year, at the hour daylight time
ends. See new docs for details.
ends. See new docs for details. In short, the new behavior mimics
the local wall clock's behavior of repeating an hour in local time.
dt.astimezone() can no longer be used to convert between naive and aware
datetime objects. If you merely want to attach, or remove, a tzinfo
object, without any conversion of date and time members, use
dt.replace(tzinfo=whatever) instead, where "whatever" is None or a
tzinfo subclass instance.
A new method tzinfo.fromutc(dt) can be overridden in tzinfo subclasses
to give complete control over how a UTC time is to be converted to
a local time. The default astimezone() implementation calls fromutc()
as its last step, so a tzinfo subclass can affect that too by overriding
fromutc(). It's expected that the default fromutc() implementation will
be suitable as-is for "almost all" time zone subclasses, but the
creativity of political time zone fiddling appears unbounded -- fromutc()
allows the highly motivated to emulate any scheme expressible in Python.
The constructors building a datetime from a timestamp could raise
ValueError if the platform C localtime()/gmtime() inserted "leap