[Python-checkins] python/dist/src/Lib/test test_datetime.py,1.16,1.17
tim_one@users.sourceforge.net
tim_one@users.sourceforge.net
Tue, 31 Dec 2002 09:36:58 -0800
Update of /cvsroot/python/python/dist/src/Lib/test
In directory sc8-pr-cvs1:/tmp/cvs-serv6382/python/Lib/test
Modified Files:
test_datetime.py
Log Message:
A new, and much hairier, implementation of astimezone(), building on
an idea from Guido. This restores that the datetime implementation
never passes a datetime d to a tzinfo method unless d.tzinfo is the
tzinfo instance whose method is being called. That in turn allows
enormous simplifications in user-written tzinfo classes (see the Python
sandbox US.py and EU.py for fully fleshed-out examples).
d.astimezone(tz) also raises ValueError now if d lands in the one hour
of the year that can't be expressed in tz (this can happen iff tz models
both standard and daylight time). That it used to return a nonsense
result always ate at me, and it turned out that it seemed impossible to
force a consistent nonsense result under the new implementation (which
doesn't know anything about how tzinfo classes implement their methods --
it can only infer properties indirectly). Guido doesn't like this --
expect it to change.
New tests of conversion between adjacent DST-aware timezones don't pass
yet, and are commented out.
Running the datetime tests in a loop under a debug build leaks 9
references per test run, but I don't believe the datetime code is the
cause (it didn't leak the last time I changed the C code, and the leak
is the same if I disable all the tests that invoke the only function
that changed here). I'll pursue that next.
Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/test/test_datetime.py,v
retrieving revision 1.16
retrieving revision 1.17
diff -C2 -d -r1.16 -r1.17
*** test_datetime.py 30 Dec 2002 20:52:32 -0000 1.16
--- test_datetime.py 31 Dec 2002 17:36:56 -0000 1.17
***************
*** 2561,2574 ****
# the cases.
return ZERO
!
! convert_endpoints_to_utc = False
! if dt.tzinfo is not self:
! # Convert dt to UTC.
! offset = dt.utcoffset()
! if offset is None:
! # Again, an exception instead may be sensible.
! return ZERO
! convert_endpoints_to_utc = True
! dt -= offset
# Find first Sunday in April.
--- 2561,2565 ----
# the cases.
return ZERO
! assert dt.tzinfo is self
# Find first Sunday in April.
***************
*** 2580,2587 ****
assert end.weekday() == 6 and end.month == 10 and end.day >= 25
- if convert_endpoints_to_utc:
- start -= self.stdoffset # start is in std time
- end -= self.stdoffset + HOUR # end is in DST time
-
# Can't compare naive to aware objects, so strip the timezone from
# dt first.
--- 2571,2574 ----
***************
*** 2591,2596 ****
return ZERO
! Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
! Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
utc_real = FixedOffset(0, "UTC", 0)
# For better test coverage, we want another flavor of UTC that's west of
--- 2578,2585 ----
return ZERO
! Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
! Central = USTimeZone(-6, "Central", "CST", "CDT")
! Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
! Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
utc_real = FixedOffset(0, "UTC", 0)
# For better test coverage, we want another flavor of UTC that's west of
***************
*** 2603,2606 ****
--- 2592,2667 ----
dstoff = datetimetz(2002, 10, 27, 2)
+
+ # Check a time that's inside DST.
+ def checkinside(self, dt, tz, utc, dston, dstoff):
+ self.assertEqual(dt.dst(), HOUR)
+
+ # Conversion to our own timezone is always an identity.
+ self.assertEqual(dt.astimezone(tz), dt)
+ # Conversion to None is always the same as stripping tzinfo.
+ self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
+
+ asutc = dt.astimezone(utc)
+ there_and_back = asutc.astimezone(tz)
+
+ # Conversion to UTC and back isn't always an identity here,
+ # because there are redundant spellings (in local time) of
+ # UTC time when DST begins: the clock jumps from 1:59:59
+ # to 3:00:00, and a local time of 2:MM:SS doesn't really
+ # make sense then. The classes above treat 2:MM:SS as
+ # daylight time then (it's "after 2am"), really an alias
+ # for 1:MM:SS standard time. The latter form is what
+ # conversion back from UTC produces.
+ if dt.date() == dston.date() and dt.hour == 2:
+ # We're in the redundant hour, and coming back from
+ # UTC gives the 1:MM:SS standard-time spelling.
+ self.assertEqual(there_and_back + HOUR, dt)
+ # Although during was considered to be in daylight
+ # time, there_and_back is not.
+ self.assertEqual(there_and_back.dst(), ZERO)
+ # They're the same times in UTC.
+ self.assertEqual(there_and_back.astimezone(utc),
+ dt.astimezone(utc))
+ else:
+ # 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.
+ nexthour_utc = asutc + HOUR
+ if dt.date() == dstoff.date() and dt.hour == 1:
+ # We're in the hour before DST ends. The hour after
+ # is ineffable.
+ # For concreteness, picture Eastern. during is of
+ # the form 1:MM:SS, it's daylight time, so that's
+ # 5:MM:SS UTC. Adding an hour gives 6:MM:SS UTC.
+ # Daylight time ended at 2+4 == 6:00:00 UTC, so
+ # 6:MM:SS is (correctly) taken to be standard time.
+ # But standard time is at offset -5, and that maps
+ # right back to the 1:MM:SS Eastern we started with.
+ # That's correct, too, *if* 1:MM:SS were taken as
+ # being standard time. But it's not -- on this day
+ # it's taken as daylight time.
+ self.assertRaises(ValueError,
+ nexthour_utc.astimezone, tz)
+ else:
+ nexthour_tz = nexthour_utc.astimezone(utc)
+ self.assertEqual(nexthour_tz - dt, HOUR)
+
+ # Check a time that's outside DST.
+ def checkoutside(self, dt, tz, utc):
+ self.assertEqual(dt.dst(), ZERO)
+
+ # Conversion to our own timezone is always an identity.
+ self.assertEqual(dt.astimezone(tz), dt)
+ # Conversion to None is always the same as stripping tzinfo.
+ self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
+
def convert_between_tz_and_utc(self, tz, utc):
dston = self.dston.replace(tzinfo=tz)
***************
*** 2612,2686 ****
timedelta(microseconds=1)):
! for during in dston, dston + delta, dstoff - delta:
! self.assertEqual(during.dst(), HOUR)
!
! # Conversion to our own timezone is always an identity.
! self.assertEqual(during.astimezone(tz), during)
! # Conversion to None is always the same as stripping tzinfo.
! self.assertEqual(during.astimezone(None),
! during.replace(tzinfo=None))
!
! asutc = during.astimezone(utc)
! there_and_back = asutc.astimezone(tz)
!
! # Conversion to UTC and back isn't always an identity here,
! # because there are redundant spellings (in local time) of
! # UTC time when DST begins: the clock jumps from 1:59:59
! # to 3:00:00, and a local time of 2:MM:SS doesn't really
! # make sense then. The classes above treat 2:MM:SS as
! # daylight time then (it's "after 2am"), really an alias
! # for 1:MM:SS standard time. The latter form is what
! # conversion back from UTC produces.
! if during.date() == dston.date() and during.hour == 2:
! # We're in the redundant hour, and coming back from
! # UTC gives the 1:MM:SS standard-time spelling.
! self.assertEqual(there_and_back + HOUR, during)
! # Although during was considered to be in daylight
! # time, there_and_back is not.
! self.assertEqual(there_and_back.dst(), ZERO)
! # They're the same times in UTC.
! self.assertEqual(there_and_back.astimezone(utc),
! during.astimezone(utc))
! else:
! # We're not in the redundant hour.
! self.assertEqual(during, 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.
! nexthour_utc = asutc + HOUR
! nexthour_tz = nexthour_utc.astimezone(tz)
! if during.date() == dstoff.date() and during.hour == 1:
! # We're in the hour before DST ends. The hour after
! # is ineffable.
! # For concreteness, picture Eastern. during is of
! # the form 1:MM:SS, it's daylight time, so that's
! # 5:MM:SS UTC. Adding an hour gives 6:MM:SS UTC.
! # Daylight time ended at 2+4 == 6:00:00 UTC, so
! # 6:MM:SS is (correctly) taken to be standard time.
! # But standard time is at offset -5, and that maps
! # right back to the 1:MM:SS Eastern we started with.
! # That's correct, too, *if* 1:MM:SS were taken as
! # being standard time. But it's not -- on this day
! # it's taken as daylight time.
! self.assertEqual(during, nexthour_tz)
! else:
! self.assertEqual(nexthour_tz - during, HOUR)
!
! for outside in dston - delta, dstoff, dstoff + delta:
! self.assertEqual(outside.dst(), ZERO)
! there_and_back = outside.astimezone(utc).astimezone(tz)
! self.assertEqual(outside, there_and_back)
! # Conversion to our own timezone is always an identity.
! self.assertEqual(outside.astimezone(tz), outside)
! # Conversion to None is always the same as stripping tzinfo.
! self.assertEqual(outside.astimezone(None),
! outside.replace(tzinfo=None))
def test_easy(self):
--- 2673,2683 ----
timedelta(microseconds=1)):
! self.checkinside(dston, tz, utc, dston, dstoff)
! for during in dston + delta, dstoff - delta:
! self.checkinside(during, tz, utc, dston, dstoff)
! self.checkoutside(dstoff, tz, utc)
! for outside in dston - delta, dstoff + delta:
! self.checkoutside(outside, tz, utc)
def test_easy(self):
***************
*** 2695,2698 ****
--- 2692,2698 ----
self.convert_between_tz_and_utc(Eastern, Pacific)
self.convert_between_tz_and_utc(Pacific, Eastern)
+ # XXX These fail!
+ #self.convert_between_tz_and_utc(Eastern, Central)
+ #self.convert_between_tz_and_utc(Central, Eastern)