"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} \begin{methoddesc}{astimezone}{tz}
Return a \class{datetime} object with new \member{tzinfo} member Return a \class{datetime} object with new \member{tzinfo} member
\var{tz}. \var{tz}, adjusting the date and time members so the result is the
\var{tz} must be \code{None}, or an instance of a \class{tzinfo} subclass. same UTC time as \var{self}, but in \var{tz}'s local time.
If \var{tz} is \code{None}, \var{self} is naive,
or \code{self.tzinfo}\ is \var{tz}, \var{tz} must be an instance of a \class{tzinfo} subclass, and its
\code{self.astimezone(tz)} is equivalent to \method{utcoffset()} and \method{dst()} methods must not return
\code{self.replace(tzinfo=tz)}: a new time zone object is attached \code{None}. \var{self} must be aware (\code{\var{self}.tzinfo} must
without any conversion of date or time members. Else \code{self.tzinfo} not be \code{None}, and \code{\var{self}.utcoffset()} must not return
and \var{tz} must implement the \method{utcoffset()} and \method{dst()} \code{None}).
\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 If code{\var{self}.tzinfo} is \var{tz},
same UTC time as \var{self}: after \code{astz = dt.astimezone(tz)}, \code{\var{self}.astimezone(\var{tz})} is equal to \var{self}: no
\code{astz - astz.utcoffset()} will usually have the same date and time adjustment of date or time members is performed.
members as \code{dt - dt.utcoffset()}. The discussion of class Else the result is local time in time zone \var{tz}, representing the
\class{tzinfo} explains the cases at Daylight Saving Time same UTC time as \var{self}: after \code{\var{astz} =
transition boundaries where this cannot be achieved (an issue only if \var{dt}.astimezone(\var{tz})},
\var{tz} models both standard and daylight time). \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} \end{methoddesc}
\begin{methoddesc}{utcoffset}{} \begin{methoddesc}{utcoffset}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else 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} \end{methoddesc}
\begin{methoddesc}{tzname}{} \begin{methoddesc}{tzname}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else 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} \end{methoddesc}
\begin{methoddesc}{timetuple}{} \begin{methoddesc}{timetuple}{}
@ -989,17 +1028,25 @@ Instance methods:
\begin{methoddesc}{utcoffset}{} \begin{methoddesc}{utcoffset}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else 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} \end{methoddesc}
\begin{methoddesc}{dst}{} \begin{methoddesc}{dst}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else 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} \end{methoddesc}
\begin{methoddesc}{tzname}{} \begin{methoddesc}{tzname}{}
If \member{tzinfo} is \code{None}, returns \code{None}, else 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} \end{methoddesc}
@ -1066,37 +1113,58 @@ implement all of them.
example, \method{datetime.timetuple()} calls its \member{tzinfo} example, \method{datetime.timetuple()} calls its \member{tzinfo}
member's \method{dst()} method to determine how the member's \method{dst()} method to determine how the
\member{tm_isdst} flag should be set, and \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. DST changes when crossing time zones.
An instance \var{tz} of a \class{tzinfo} subclass that models both An instance \var{tz} of a \class{tzinfo} subclass that models both
standard and daylight times must be consistent in this sense: 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} must return the same result for every \class{datetime} \var{dt}
with \code{dt.tzinfo==tz} For sane \class{tzinfo} subclasses, this with \code{\var{dt}.tzinfo==\var{tz}} For sane \class{tzinfo}
expression yields the time zone's "standard offset", which should not subclasses, this expression yields the time zone's "standard offset",
depend on the date or the time, but only on geographic location. The which should not depend on the date or the time, but only on geographic
implementation of \method{datetime.astimezone()} relies on this, but location. The implementation of \method{datetime.astimezone()} relies
cannot detect violations; it's the programmer's responsibility to on this, but cannot detect violations; it's the programmer's
ensure it. 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 The default implementation of \method{dst()} raises
\exception{NotImplementedError}. \exception{NotImplementedError}.
\end{methoddesc} \end{methoddesc}
\begin{methoddesc}{tzname}{self, dt} \begin{methoddesc}{tzname}{self, dt}
Return the timezone name corresponding to the \class{datetime} Return the time zone name corresponding to the \class{datetime}
object represented object \var{dt}, as a string.
by \var{dt}, as a string. Nothing about string names is defined by the Nothing about string names is defined by the
\module{datetime} module, and there's no requirement that it mean anything \module{datetime} module, and there's no requirement that it mean
in particular. For example, "GMT", "UTC", "-500", "-5:00", "EDT", anything in particular. For example, "GMT", "UTC", "-500", "-5:00",
"US/Eastern", "America/New York" are all valid replies. Return "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 \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 rather than a fixed string primarily because some \class{tzinfo}
will wish to return different names depending on the specific value subclasses will wish to return different names depending on the specific
of \var{dt} passed, especially if the \class{tzinfo} class is value of \var{dt} passed, especially if the \class{tzinfo} class is
accounting for daylight time. accounting for daylight time.
The default implementation of \method{tzname()} raises 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 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 best response. For example, returning \code{None} is appropriate if the
class wishes to say that time objects don't participate in 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 to return the standard UTC offset, as there is no other convention for
discovering the standard offset. 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, the \class{tzinfo} methods interpret \var{dt} as being in local time,
and not need worry about objects in other timezones. 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: Example \class{tzinfo} classes:
\verbatiminput{tzinfo-examples.py} \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 day, so \code{astimezone(Eastern)} won't deliver a result with
\code{hour==2} on the \code{hour==2} on the
day DST begins. In order for \method{astimezone()} to make this 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. 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: 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 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 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 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{astimezone()} to make this guarantee, the \method{tzinfo.dst()}
\method{dst()} method must consider times in the "repeated hour" to be in method must consider times in the "repeated hour" to be in
standard time. This is easily arranged, as in the example, by expressing standard time. This is easily arranged, as in the example, by expressing
DST switch times in the time zone's standard local time. DST switch times in the time zone's standard local time.
@ -1235,9 +1347,7 @@ Struct typedefs:
PyDateTime_Date PyDateTime_Date
PyDateTime_DateTime PyDateTime_DateTime
PyDateTime_DateTimeTZ
PyDateTime_Time PyDateTime_Time
PyDateTime_TimeTZ
PyDateTime_Delta PyDateTime_Delta
PyDateTime_TZInfo PyDateTime_TZInfo
@ -1249,15 +1359,9 @@ Type-check macros:
PyDateTime_Check(op) PyDateTime_Check(op)
PyDateTime_CheckExact(op) PyDateTime_CheckExact(op)
PyDateTimeTZ_Check(op)
PyDateTimeTZ_CheckExact(op)
PyTime_Check(op) PyTime_Check(op)
PyTime_CheckExact(op) PyTime_CheckExact(op)
PyTimeTZ_Check(op)
PyTimeTZ_CheckExact(op)
PyDelta_Check(op) PyDelta_Check(op)
PyDelta_CheckExact(op) PyDelta_CheckExact(op)
@ -1269,18 +1373,18 @@ Accessor macros:
All objects are immutable, so accessors are read-only. All macros All objects are immutable, so accessors are read-only. All macros
return ints: return ints:
For \class{date}, \class{datetime}, and \class{datetimetz} instances: For \class{date} and \class{datetime} instances:
PyDateTime_GET_YEAR(o) PyDateTime_GET_YEAR(o)
PyDateTime_GET_MONTH(o) PyDateTime_GET_MONTH(o)
PyDateTime_GET_DAY(o) PyDateTime_GET_DAY(o)
For \class{datetime} and \class{datetimetz} instances: For \class{datetime} instances:
PyDateTime_DATE_GET_HOUR(o) PyDateTime_DATE_GET_HOUR(o)
PyDateTime_DATE_GET_MINUTE(o) PyDateTime_DATE_GET_MINUTE(o)
PyDateTime_DATE_GET_SECOND(o) PyDateTime_DATE_GET_SECOND(o)
PyDateTime_DATE_GET_MICROSECOND(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_HOUR(o)
PyDateTime_TIME_GET_MINUTE(o) PyDateTime_TIME_GET_MINUTE(o)
PyDateTime_TIME_GET_SECOND(o) PyDateTime_TIME_GET_SECOND(o)

View File

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

View File

@ -45,7 +45,7 @@ Extension modules
microsecond <http://www.python.org/sf/661086>. This repairs an microsecond <http://www.python.org/sf/661086>. This repairs an
irritation most likely seen on Windows systems. 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 ValueError is raised if tz.dst(dt) returns None (2.3a1 treated it
as 0 instead, but a tzinfo subclass wishing to participate in as 0 instead, but a tzinfo subclass wishing to participate in
time zone conversion has to take a stand on whether it supports 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 The example tzinfo class for local time had a bug. It was replaced
by a later example coded by Guido. 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 input datetime has no UTC equivalent in tz. For typical "hybrid" time
zones (a single tzinfo subclass modeling both standard and daylight zones (a single tzinfo subclass modeling both standard and daylight
time), this case can arise one hour per year, at the hour daylight time 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 The constructors building a datetime from a timestamp could raise
ValueError if the platform C localtime()/gmtime() inserted "leap ValueError if the platform C localtime()/gmtime() inserted "leap