Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f196a0a

Browse files
committed
"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>).
1 parent 51f3f1b commit f196a0a

3 files changed

Lines changed: 180 additions & 60 deletions

File tree

Doc/lib/libdatetime.tex

Lines changed: 157 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -734,32 +734,71 @@ \subsection{\class{datetime} Objects \label{datetime-datetime}}
734734

735735
\begin{methoddesc}{astimezone}{tz}
736736
Return a \class{datetime} object with new \member{tzinfo} member
737-
\var{tz}.
738-
\var{tz} must be \code{None}, or an instance of a \class{tzinfo} subclass.
739-
If \var{tz} is \code{None}, \var{self} is naive,
740-
or \code{self.tzinfo}\ is \var{tz},
741-
\code{self.astimezone(tz)} is equivalent to
742-
\code{self.replace(tzinfo=tz)}: a new time zone object is attached
743-
without any conversion of date or time members. Else \code{self.tzinfo}
744-
and \var{tz} must implement the \method{utcoffset()} and \method{dst()}
745-
\class{tzinfo} methods, and the date and time members are adjusted so
746-
that the result is local time in time zone \var{tz}, representing the
747-
same UTC time as \var{self}: after \code{astz = dt.astimezone(tz)},
748-
\code{astz - astz.utcoffset()} will usually have the same date and time
749-
members as \code{dt - dt.utcoffset()}. The discussion of class
750-
\class{tzinfo} explains the cases at Daylight Saving Time
751-
transition boundaries where this cannot be achieved (an issue only if
752-
\var{tz} models both standard and daylight time).
737+
\var{tz}, adjusting the date and time members so the result is the
738+
same UTC time as \var{self}, but in \var{tz}'s local time.
739+
740+
\var{tz} must be an instance of a \class{tzinfo} subclass, and its
741+
\method{utcoffset()} and \method{dst()} methods must not return
742+
\code{None}. \var{self} must be aware (\code{\var{self}.tzinfo} must
743+
not be \code{None}, and \code{\var{self}.utcoffset()} must not return
744+
\code{None}).
745+
746+
If code{\var{self}.tzinfo} is \var{tz},
747+
\code{\var{self}.astimezone(\var{tz})} is equal to \var{self}: no
748+
adjustment of date or time members is performed.
749+
Else the result is local time in time zone \var{tz}, representing the
750+
same UTC time as \var{self}: after \code{\var{astz} =
751+
\var{dt}.astimezone(\var{tz})},
752+
\code{\var{astz} - \var{astz}.utcoffset()} will usually have the same
753+
date and time members as \code{\var{dt} - \var{dt}.utcoffset()}.
754+
The discussion of class \class{tzinfo} explains the cases at Daylight
755+
Saving Time transition boundaries where this cannot be achieved (an issue
756+
only if \var{tz} models both standard and daylight time).
757+
758+
If you merely want to attach a time zone object \var{tz} to a
759+
datetime \var{dt} without adjustment of date and time members,
760+
use \code{\var{dt}.replace(tzinfo=\var{tz})}. If
761+
you merely want to remove the time zone object from an aware datetime
762+
\var{dt} without conversion of date and time members, use
763+
\code{\var{dt}.replace(tzinfo=None)}.
764+
765+
Note that the default \method{tzinfo.fromutc()} method can be overridden
766+
in a \class{tzinfo} subclass to effect the result returned by
767+
\method{astimezone()}. Ignoring error cases, \method{astimezone()}
768+
acts like:
769+
770+
\begin{verbatim}
771+
def astimezone(self, tz):
772+
if self.tzinfo is tz:
773+
return self
774+
# Convert self to UTC, and attach the new time zone object.
775+
utc = (self - self.utcoffset()).replace(tzinfo=tz)
776+
# Convert from UTC to tz's local time.
777+
return tz.fromutc(utc)
778+
\end{verbatim}
753779
\end{methoddesc}
754780

755781
\begin{methoddesc}{utcoffset}{}
756782
If \member{tzinfo} is \code{None}, returns \code{None}, else
757-
returns \code{tzinfo.utcoffset(self)}.
783+
returns \code{\var{self}.tzinfo.utcoffset(\var{self})}, and
784+
raises an exception if the latter doesn't return \code{None}, or
785+
a \class{timedelta} object representing a whole number of minutes
786+
with magnitude less than one day.
787+
\end{methoddesc}
788+
789+
\begin{methoddesc}{dst}{}
790+
If \member{tzinfo} is \code{None}, returns \code{None}, else
791+
returns \code{\var{self}.tzinfo.dst(\var{self})}, and
792+
raises an exception if the latter doesn't return \code{None}, or
793+
a \class{timedelta} object representing a whole number of minutes
794+
with magnitude less than one day.
758795
\end{methoddesc}
759796

760797
\begin{methoddesc}{tzname}{}
761798
If \member{tzinfo} is \code{None}, returns \code{None}, else
762-
returns \code{tzinfo.tzname(self)}.
799+
returns \code{\var{self}.tzinfo.tzname(\var{self})},
800+
raises an exception if the latter doesn't return \code{None} or
801+
a string object,
763802
\end{methoddesc}
764803

765804
\begin{methoddesc}{timetuple}{}
@@ -989,17 +1028,25 @@ \subsection{\class{time} Objects \label{datetime-time}}
9891028

9901029
\begin{methoddesc}{utcoffset}{}
9911030
If \member{tzinfo} is \code{None}, returns \code{None}, else
992-
returns \code{tzinfo.utcoffset(self)}.
1031+
returns \code{\var{self}.tzinfo.utcoffset(None)}, and
1032+
raises an exception if the latter doesn't return \code{None} or
1033+
a \class{timedelta} object representing a whole number of minutes
1034+
with magnitude less than one day.
9931035
\end{methoddesc}
9941036

9951037
\begin{methoddesc}{dst}{}
9961038
If \member{tzinfo} is \code{None}, returns \code{None}, else
997-
returns \code{tzinfo.dst(self)}.
1039+
returns \code{\var{self}.tzinfo.dst(None)}, and
1040+
raises an exception if the latter doesn't return \code{None}, or
1041+
a \class{timedelta} object representing a whole number of minutes
1042+
with magnitude less than one day.
9981043
\end{methoddesc}
9991044

10001045
\begin{methoddesc}{tzname}{}
10011046
If \member{tzinfo} is \code{None}, returns \code{None}, else
1002-
returns \code{tzinfo.tzname(self)}.
1047+
returns \code{\var{self}.tzinfo.tzname(None)}, or
1048+
raises an exception if the latter doesn't return \code{None} or
1049+
a string object.
10031050
\end{methoddesc}
10041051

10051052

@@ -1066,37 +1113,58 @@ \subsection{\class{tzinfo} Objects \label{datetime-tzinfo}}
10661113
example, \method{datetime.timetuple()} calls its \member{tzinfo}
10671114
member's \method{dst()} method to determine how the
10681115
\member{tm_isdst} flag should be set, and
1069-
\method{datetime.astimezone()} calls \method{dst()} to account for
1116+
\method{tzinfo.fromutc()} calls \method{dst()} to account for
10701117
DST changes when crossing time zones.
10711118

10721119
An instance \var{tz} of a \class{tzinfo} subclass that models both
10731120
standard and daylight times must be consistent in this sense:
10741121

1075-
\code{tz.utcoffset(dt) - tz.dst(dt)}
1122+
\code{\var{tz}.utcoffset(\var{dt}) - \var{tz}.dst(\var{dt})}
10761123

10771124
must return the same result for every \class{datetime} \var{dt}
1078-
with \code{dt.tzinfo==tz} For sane \class{tzinfo} subclasses, this
1079-
expression yields the time zone's "standard offset", which should not
1080-
depend on the date or the time, but only on geographic location. The
1081-
implementation of \method{datetime.astimezone()} relies on this, but
1082-
cannot detect violations; it's the programmer's responsibility to
1083-
ensure it.
1125+
with \code{\var{dt}.tzinfo==\var{tz}} For sane \class{tzinfo}
1126+
subclasses, this expression yields the time zone's "standard offset",
1127+
which should not depend on the date or the time, but only on geographic
1128+
location. The implementation of \method{datetime.astimezone()} relies
1129+
on this, but cannot detect violations; it's the programmer's
1130+
responsibility to ensure it. If a \class{tzinfo} subclass cannot
1131+
guarantee this, it may be able to override the default implementation
1132+
of \method{tzinfo.fromutc()} to work correctly with \method{astimezone()}
1133+
regardless.
1134+
1135+
Most implementations of \method{dst()} will probably look like one
1136+
of these two:
1137+
1138+
\begin{verbatim}
1139+
return timedelta(0) # a fixed-offset class: doesn't account for DST
1140+
1141+
or
1142+
1143+
# Code to set dston and dstoff to the time zone's DST transition
1144+
# times based on the input dt.year, and expressed in standard local
1145+
# time. Then
1146+
1147+
if dston <= dt.replace(tzinfo=None) < dstoff:
1148+
return timedelta(hours=1)
1149+
else:
1150+
return timedelta(0)
1151+
\end{verbatim}
10841152

10851153
The default implementation of \method{dst()} raises
10861154
\exception{NotImplementedError}.
10871155
\end{methoddesc}
10881156

10891157
\begin{methoddesc}{tzname}{self, dt}
1090-
Return the timezone name corresponding to the \class{datetime}
1091-
object represented
1092-
by \var{dt}, as a string. Nothing about string names is defined by the
1093-
\module{datetime} module, and there's no requirement that it mean anything
1094-
in particular. For example, "GMT", "UTC", "-500", "-5:00", "EDT",
1095-
"US/Eastern", "America/New York" are all valid replies. Return
1158+
Return the time zone name corresponding to the \class{datetime}
1159+
object \var{dt}, as a string.
1160+
Nothing about string names is defined by the
1161+
\module{datetime} module, and there's no requirement that it mean
1162+
anything in particular. For example, "GMT", "UTC", "-500", "-5:00",
1163+
"EDT", "US/Eastern", "America/New York" are all valid replies. Return
10961164
\code{None} if a string name isn't known. Note that this is a method
1097-
rather than a fixed string primarily because some \class{tzinfo} objects
1098-
will wish to return different names depending on the specific value
1099-
of \var{dt} passed, especially if the \class{tzinfo} class is
1165+
rather than a fixed string primarily because some \class{tzinfo}
1166+
subclasses will wish to return different names depending on the specific
1167+
value of \var{dt} passed, especially if the \class{tzinfo} class is
11001168
accounting for daylight time.
11011169

11021170
The default implementation of \method{tzname()} raises
@@ -1113,7 +1181,7 @@ \subsection{\class{tzinfo} Objects \label{datetime-tzinfo}}
11131181
When \code{None} is passed, it's up to the class designer to decide the
11141182
best response. For example, returning \code{None} is appropriate if the
11151183
class wishes to say that time objects don't participate in the
1116-
\class{tzinfo} protocol. It may be more useful for \code{utcoffset(None)}
1184+
\class{tzinfo} protocols. It may be more useful for \code{utcoffset(None)}
11171185
to return the standard UTC offset, as there is no other convention for
11181186
discovering the standard offset.
11191187

@@ -1124,6 +1192,50 @@ \subsection{\class{tzinfo} Objects \label{datetime-tzinfo}}
11241192
the \class{tzinfo} methods interpret \var{dt} as being in local time,
11251193
and not need worry about objects in other timezones.
11261194

1195+
There is one more \class{tzinfo} method that a subclass may wish to
1196+
override:
1197+
1198+
\begin{methoddesc}{fromutc}{self, dt}
1199+
This is called from the default \class{datetime.astimezone()}
1200+
implementation. When called from that, \code{\var{dt}.tzinfo} is
1201+
\var{self}, and \var{dt}'s date and time members are to be viewed as
1202+
expressing a UTC time. The purpose of \method{fromutc()} is to
1203+
adjust the date and time members, returning an equivalent datetime in
1204+
\var{self}'s local time.
1205+
1206+
Most \class{tzinfo} subclasses should be able to inherit the default
1207+
\method{fromutc()} implementation without problems. It's strong enough
1208+
to handle fixed-offset time zones, and time zones accounting for both
1209+
standard and daylight time, and the latter even if the DST transition
1210+
times differ in different years. An example of a time zone the default
1211+
\method{fromutc()} implementation may not handle correctly in all cases
1212+
is one where the standard offset (from UTC) depends on the specific date
1213+
and time passed, which can happen for political reasons.
1214+
The default implementations of \method{astimezone()} and
1215+
\method{fromutc()} may not produce the result you want if the result is
1216+
one of the hours straddling the moment the standard offset changes.
1217+
1218+
Skipping code for error cases, the default \method{fromutc()}
1219+
implementation acts like:
1220+
1221+
\begin{verbatim}
1222+
def fromutc(self, dt):
1223+
# raise ValueError error if dt.tzinfo is not self
1224+
dtoff = dt.utcoffset()
1225+
dtdst = dt.dst()
1226+
# raise ValueError if dtoff is None or dtdst is None
1227+
delta = dtoff - dtdst # this is self's standard offset
1228+
if delta:
1229+
dt += delta # convert to standard local time
1230+
dtdst = dt.dst()
1231+
# raise ValueError if dtdst is None
1232+
if dtdst:
1233+
return dt + dtdst
1234+
else:
1235+
return dt
1236+
\end{verbatim}
1237+
\end{methoddesc}
1238+
11271239
Example \class{tzinfo} classes:
11281240

11291241
\verbatiminput{tzinfo-examples.py}
@@ -1150,7 +1262,7 @@ \subsection{\class{tzinfo} Objects \label{datetime-tzinfo}}
11501262
day, so \code{astimezone(Eastern)} won't deliver a result with
11511263
\code{hour==2} on the
11521264
day DST begins. In order for \method{astimezone()} to make this
1153-
guarantee, the \class{tzinfo} \method{dst()} method must consider times
1265+
guarantee, the \method{rzinfo.dst()} method must consider times
11541266
in the "missing hour" (2:MM for Eastern) to be in daylight time.
11551267

11561268
When DST ends (the "end" line), there's a potentially worse problem:
@@ -1162,8 +1274,8 @@ \subsection{\class{tzinfo} Objects \label{datetime-tzinfo}}
11621274
the local clock's behavior by mapping two adjacent UTC hours into the
11631275
same local hour then. In the Eastern example, UTC times of the form
11641276
5:MM and 6:MM both map to 1:MM when converted to Eastern. In order for
1165-
\method{astimezone()} to make this guarantee, the \class{tzinfo}
1166-
\method{dst()} method must consider times in the "repeated hour" to be in
1277+
\method{astimezone()} to make this guarantee, the \method{tzinfo.dst()}
1278+
method must consider times in the "repeated hour" to be in
11671279
standard time. This is easily arranged, as in the example, by expressing
11681280
DST switch times in the time zone's standard local time.
11691281

@@ -1235,9 +1347,7 @@ \subsection{C API}
12351347

12361348
PyDateTime_Date
12371349
PyDateTime_DateTime
1238-
PyDateTime_DateTimeTZ
12391350
PyDateTime_Time
1240-
PyDateTime_TimeTZ
12411351
PyDateTime_Delta
12421352
PyDateTime_TZInfo
12431353

@@ -1249,15 +1359,9 @@ \subsection{C API}
12491359
PyDateTime_Check(op)
12501360
PyDateTime_CheckExact(op)
12511361

1252-
PyDateTimeTZ_Check(op)
1253-
PyDateTimeTZ_CheckExact(op)
1254-
12551362
PyTime_Check(op)
12561363
PyTime_CheckExact(op)
12571364

1258-
PyTimeTZ_Check(op)
1259-
PyTimeTZ_CheckExact(op)
1260-
12611365
PyDelta_Check(op)
12621366
PyDelta_CheckExact(op)
12631367

@@ -1269,18 +1373,18 @@ \subsection{C API}
12691373
All objects are immutable, so accessors are read-only. All macros
12701374
return ints:
12711375

1272-
For \class{date}, \class{datetime}, and \class{datetimetz} instances:
1376+
For \class{date} and \class{datetime} instances:
12731377
PyDateTime_GET_YEAR(o)
12741378
PyDateTime_GET_MONTH(o)
12751379
PyDateTime_GET_DAY(o)
12761380

1277-
For \class{datetime} and \class{datetimetz} instances:
1381+
For \class{datetime} instances:
12781382
PyDateTime_DATE_GET_HOUR(o)
12791383
PyDateTime_DATE_GET_MINUTE(o)
12801384
PyDateTime_DATE_GET_SECOND(o)
12811385
PyDateTime_DATE_GET_MICROSECOND(o)
12821386

1283-
For \class{time} and \class{timetz} instances:
1387+
For \class{time} instances:
12841388
PyDateTime_TIME_GET_HOUR(o)
12851389
PyDateTime_TIME_GET_MINUTE(o)
12861390
PyDateTime_TIME_GET_SECOND(o)

Doc/lib/tzinfo-examples.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class FixedOffset(tzinfo):
2727
"""Fixed offset in minutes east from UTC."""
2828

2929
def __init__(self, offset, name):
30-
self.__offset = timdelta(minutes = offset)
30+
self.__offset = timedelta(minutes = offset)
3131
self.__name = name
3232

3333
def utcoffset(self, dt):
@@ -116,9 +116,9 @@ def utcoffset(self, dt):
116116
def dst(self, dt):
117117
if dt is None or dt.tzinfo is None:
118118
# An exception may be sensible here, in one or both cases.
119-
# It depends on how you want to treat them. The astimezone()
120-
# implementation always passes a datetime with
121-
# dt.tzinfo == self.
119+
# It depends on how you want to treat them. The default
120+
# fromutc() implementation (called by the default astimezone()
121+
# implementation) passes a datetime with dt.tzinfo is self.
122122
return ZERO
123123
assert dt.tzinfo is self
124124

Misc/NEWS

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Extension modules
4545
microsecond <http://www.python.org/sf/661086>. This repairs an
4646
irritation most likely seen on Windows systems.
4747

48-
In dt.asdatetime(tz), if tz.utcoffset(dt) returns a duration,
48+
In dt.astimezone(tz), if tz.utcoffset(dt) returns a duration,
4949
ValueError is raised if tz.dst(dt) returns None (2.3a1 treated it
5050
as 0 instead, but a tzinfo subclass wishing to participate in
5151
time zone conversion has to take a stand on whether it supports
@@ -60,11 +60,27 @@ Extension modules
6060
The example tzinfo class for local time had a bug. It was replaced
6161
by a later example coded by Guido.
6262

63-
datetimetz.astimezone(tz) no longer raises an exception when the
63+
datetime.astimezone(tz) no longer raises an exception when the
6464
input datetime has no UTC equivalent in tz. For typical "hybrid" time
6565
zones (a single tzinfo subclass modeling both standard and daylight
6666
time), this case can arise one hour per year, at the hour daylight time
67-
ends. See new docs for details.
67+
ends. See new docs for details. In short, the new behavior mimics
68+
the local wall clock's behavior of repeating an hour in local time.
69+
70+
dt.astimezone() can no longer be used to convert between naive and aware
71+
datetime objects. If you merely want to attach, or remove, a tzinfo
72+
object, without any conversion of date and time members, use
73+
dt.replace(tzinfo=whatever) instead, where "whatever" is None or a
74+
tzinfo subclass instance.
75+
76+
A new method tzinfo.fromutc(dt) can be overridden in tzinfo subclasses
77+
to give complete control over how a UTC time is to be converted to
78+
a local time. The default astimezone() implementation calls fromutc()
79+
as its last step, so a tzinfo subclass can affect that too by overriding
80+
fromutc(). It's expected that the default fromutc() implementation will
81+
be suitable as-is for "almost all" time zone subclasses, but the
82+
creativity of political time zone fiddling appears unbounded -- fromutc()
83+
allows the highly motivated to emulate any scheme expressible in Python.
6884

6985
The constructors building a datetime from a timestamp could raise
7086
ValueError if the platform C localtime()/gmtime() inserted "leap

0 commit comments

Comments
 (0)