summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libdatetime.tex68
-rw-r--r--Doc/lib/tzinfo-examples.py2
-rw-r--r--Lib/test/test_datetime.py47
-rw-r--r--Modules/datetimemodule.c24
4 files changed, 68 insertions, 73 deletions
diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex
index 18edc32..2421fa7 100644
--- a/Doc/lib/libdatetime.tex
+++ b/Doc/lib/libdatetime.tex
@@ -30,7 +30,7 @@ the cost of ignoring some aspects of reality.
For applications requiring more, \class{datetime} and \class{time}
objects have an optional time zone information member,
-\member{tzinfo}, that can contain an instance of a subclass of
+\member{tzinfo}, that can contain an instance of a subclass of
the abstract \class{tzinfo} class. These \class{tzinfo} objects
capture information about the offset from UTC time, the time zone
name, and whether Daylight Saving Time is in effect. Note that no
@@ -1048,8 +1048,10 @@ implement all of them.
If \method{utcoffset()} does not return \code{None},
\method{dst()} should not return \code{None} either.
-\end{methoddesc}
+ The default implementation of \method{utcoffset()} raises
+ \exception{NotImplementedError}.
+\end{methoddesc}
\begin{methoddesc}{dst}{self, dt}
Return the daylight saving time (DST) adjustment, in minutes east of
@@ -1060,7 +1062,7 @@ implement all of them.
Note that DST offset, if applicable, has
already been added to the UTC offset returned by
\method{utcoffset()}, so there's no need to consult \method{dst()}
- unless you're interested in displaying DST info separately. For
+ unless you're interested in obtaining DST info separately. For
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
@@ -1080,6 +1082,10 @@ implement all of them.
cannot detect violations; it's the programmer's responsibility to
ensure it.
+ 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
@@ -1092,8 +1098,9 @@ implement all of them.
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.
-\end{methoddesc}
+ The default implementation of \method{tzname()} raises
+ \exception{NotImplementedError}.
\end{methoddesc}
These methods are called by a \class{datetime} or \class{time} object,
@@ -1106,21 +1113,23 @@ 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. In other applications, it may be more useful
-for \code{utcoffset(None)} to return the standard UTC offset.
+\class{tzinfo} protocol. 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.
When a \class{datetime} object is passed in response to a
\class{datetime} method, \code{dt.tzinfo} is the same object as
\var{self}. \class{tzinfo} methods can rely on this, unless
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 to worry about objects in other timezones.
+and not need worry about objects in other timezones.
Example \class{tzinfo} classes:
\verbatiminput{tzinfo-examples.py}
-Note that there are unavoidable subtleties twice per year in a tzinfo
+Note that there are unavoidable subtleties twice per year in a
+\class{tzinfo}
subclass accounting for both standard and daylight time, at the DST
transition points. For concreteness, consider US Eastern (UTC -0500),
where EDT begins the minute after 1:59 (EST) on the first Sunday in
@@ -1140,32 +1149,29 @@ When DST starts (the "start" line), the local 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, so \code{astimezone(Eastern)} won't deliver a result with
\code{hour==2} on the
-day DST begins. How an Eastern instance chooses to interpret 2:MM on
-that day is its business. The example Eastern implementation above
-chose to
-consider it as a time in EDT, simply because it "looks like it's
-after 2:00", and so synonymous with the EST 1:MM times on that day.
-Your Eastern class may wish, for example, to raise an exception instead
-when it sees a 2:MM time on the day EDT begins.
+day DST begins. In order for \method{astimezone()} to make this
+guarantee, the \class{tzinfo} \method{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:
-there's an hour that can't be spelled unambiguously in local wall time, the
-hour beginning at the moment DST ends. In this example, that's times of
-the form 6:MM UTC on the day daylight time ends. The local wall clock
+there's an hour that can't be spelled unambiguously in local wall time:
+the last hour of daylight time. In Eastern, that's times of
+the form 5:MM UTC on the day daylight time ends. The local wall clock
leaps from 1:59 (daylight time) back to 1:00 (standard time) again.
-1:MM is taken as daylight time (it's "before 2:00"), so maps to 5:MM UTC.
-2:MM is taken as standard time (it's "after 2:00"), so maps to 7:MM UTC.
-There is no local time that maps to 6:MM UTC on this day.
-
-Just as the wall clock does, \code{astimezone(Eastern)} maps both UTC
-hours 5:MM
-and 6:MM to Eastern hour 1:MM on this day. However, this result is
-ambiguous (there's no way for Eastern to know which repetition of 1:MM
-is intended). Applications that can't bear such ambiguity
-should avoid using hybrid tzinfo classes; there are no
-ambiguities when using UTC, or any other fixed-offset tzinfo subclass
-(such as a class representing only EST (fixed offset -5 hours), or only
-EDT (fixed offset -4 hours)).
+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
+standard time. This is easily arranged, as in the example, by expressing
+DST switch times in the time zone's standard local time.
+
+Applications that can't bear such ambiguities should avoid using hybrid
+\class{tzinfo} subclasses; there are no ambiguities when using UTC, or
+any other fixed-offset \class{tzinfo} subclass (such as a class
+representing only EST (fixed offset -5 hours), or only EDT (fixed offset
+-4 hours)).
\subsection{\method{strftime()} Behavior}
diff --git a/Doc/lib/tzinfo-examples.py b/Doc/lib/tzinfo-examples.py
index cea0309..b488dd0 100644
--- a/Doc/lib/tzinfo-examples.py
+++ b/Doc/lib/tzinfo-examples.py
@@ -91,7 +91,7 @@ def first_sunday_on_or_after(dt):
DSTSTART = datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
# which is the first Sunday on or after Oct 25.
-DSTEND = datetime(1, 10, 25, 2)
+DSTEND = datetime(1, 10, 25, 1)
class USTimeZone(tzinfo):
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index a46e3ca..8a8d315 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -2561,8 +2561,10 @@ DAY = timedelta(days=1)
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
DSTSTART = datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
-# which is the first Sunday on or after Oct 25.
-DSTEND = datetime(1, 10, 25, 2)
+# which is the first Sunday on or after Oct 25. Because we view 1:MM as
+# being standard time on that day, there is no spelling in local time of
+# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
+DSTEND = datetime(1, 10, 25, 1)
class USTimeZone(tzinfo):
@@ -2616,9 +2618,9 @@ utc_real = FixedOffset(0, "UTC", 0)
utc_fake = FixedOffset(-12*60, "UTCfake", 0)
class TestTimezoneConversions(unittest.TestCase):
- # The DST switch times for 2002, in local time.
+ # The DST switch times for 2002, in std time.
dston = datetime(2002, 4, 7, 2)
- dstoff = datetime(2002, 10, 27, 2)
+ dstoff = datetime(2002, 10, 27, 1)
theclass = datetime
@@ -2656,25 +2658,25 @@ class TestTimezoneConversions(unittest.TestCase):
# We're not in the redundant hour.
self.assertEqual(dt, there_and_back)
- # Because we have a redundant spelling when DST begins,
- # there is (unforunately) an hour when DST ends that can't
- # be spelled at all in local time. When DST ends, the
- # clock jumps from 1:59:59 back to 1:00:00 again. The
- # hour beginning then has no spelling in local time:
- # 1:MM:SS is taken to be daylight time, and 2:MM:SS as
- # standard time. The hour 1:MM:SS standard time ==
- # 2:MM:SS daylight time can't be expressed in local time.
- # Nevertheless, we want conversion back from UTC to mimic
- # the local clock's "repeat an hour" behavior.
+ # Because we have a redundant spelling when DST begins, there is
+ # (unforunately) an hour when DST ends that can't be spelled at all in
+ # local time. When DST ends, the clock jumps from 1:59 back to 1:00
+ # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
+ # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
+ # daylight time. The hour 1:MM daylight == 0:MM standard can't be
+ # expressed in local time. Nevertheless, we want conversion back
+ # from UTC to mimic the local clock's "repeat an hour" behavior.
nexthour_utc = asutc + HOUR
nexthour_tz = nexthour_utc.astimezone(tz)
- if dt.date() == dstoff.date() and dt.hour == 1:
- # We're in the hour before DST ends. The hour after
+ if dt.date() == dstoff.date() and dt.hour == 0:
+ # We're in the hour before the last DST hour. The last DST hour
# is ineffable. We want the conversion back to repeat 1:MM.
- expected_diff = ZERO
+ self.assertEqual(nexthour_tz, dt.replace(hour=1))
+ nexthour_utc += HOUR
+ nexthour_tz = nexthour_utc.astimezone(tz)
+ self.assertEqual(nexthour_tz, dt.replace(hour=1))
else:
- expected_diff = HOUR
- self.assertEqual(nexthour_tz - dt, expected_diff)
+ self.assertEqual(nexthour_tz - dt, HOUR)
# Check a time that's outside DST.
def checkoutside(self, dt, tz, utc):
@@ -2687,6 +2689,11 @@ class TestTimezoneConversions(unittest.TestCase):
def convert_between_tz_and_utc(self, tz, utc):
dston = self.dston.replace(tzinfo=tz)
+ # Because 1:MM on the day DST ends is taken as being standard time,
+ # there is no spelling in tz for the last hour of daylight time.
+ # For purposes of the test, the last hour of DST is 0:MM, which is
+ # taken as being daylight time (and 1:MM is taken as being standard
+ # time).
dstoff = self.dstoff.replace(tzinfo=tz)
for delta in (timedelta(weeks=13),
DAY,
@@ -2759,7 +2766,7 @@ class TestTimezoneConversions(unittest.TestCase):
# wall 0:MM 1:MM 1:MM 2:MM against these
for utc in utc_real, utc_fake:
for tz in Eastern, Pacific:
- first_std_hour = self.dstoff - timedelta(hours=3) # 23:MM
+ first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
# Convert that to UTC.
first_std_hour -= tz.utcoffset(None)
# Adjust for possibly fake UTC.
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 0bd49b2..d88fc9e 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -4046,7 +4046,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
PyObject *result;
PyObject *temp;
- int selfoff, resoff, dst1, dst2;
+ int selfoff, resoff, dst1;
int none;
int delta;
@@ -4128,26 +4128,8 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
temp = new_datetime(y, m, d, hh, mm, ss, us, tzinfo);
if (temp == NULL)
goto Fail;
-
- dst2 = call_dst(tzinfo, temp, &none);
- if (dst2 == -1 && PyErr_Occurred()) {
- Py_DECREF(temp);
- goto Fail;
- }
- if (none) {
- Py_DECREF(temp);
- goto Inconsistent;
- }
-
- if (dst1 == dst2) {
- /* The normal case: we want temp, not result. */
- Py_DECREF(result);
- result = temp;
- }
- else {
- /* The "unspellable hour" at the end of DST. */
- Py_DECREF(temp);
- }
+ Py_DECREF(result);
+ result = temp;
return result;
Inconsistent: