[Python-checkins] python/nondist/sandbox/datetime datetime.py,1.141,1.142 test_datetime.py,1.94,1.95

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Fri, 03 Jan 2003 20:50:18 -0800


Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory sc8-pr-cvs1:/tmp/cvs-serv27323

Modified Files:
	datetime.py test_datetime.py 
Log Message:
A new implementation of astimezone() that does what we agreed on in all
cases, plus even tougher tests of that.  This implementation follows
the correctness proof very closely, and should also be quicker (yes,
I wrote the proof before the code, and the code proves the proof <wink>).


Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.141
retrieving revision 1.142
diff -C2 -d -r1.141 -r1.142
*** datetime.py	4 Jan 2003 00:18:45 -0000	1.141
--- datetime.py	4 Jan 2003 04:50:16 -0000	1.142
***************
*** 1618,1623 ****
                            microsecond, tzinfo)
  
!     def _inconsistent_utcoffset_error(self):
!         raise ValueError("astimezone():  tz.utcoffset() gave "
                           "inconsistent results; cannot convert")
  
--- 1618,1623 ----
                            microsecond, tzinfo)
  
!     def _inconsistent_dst(self):
!         raise ValueError("astimezone():  tz.dst() gave "
                           "inconsistent results; cannot convert")
  
***************
*** 1641,1676 ****
          # See the long comment block at the end of this file for an
          # explanation of this algorithm.  That it always works requires a
!         # pretty intricate proof.
          otdst = other.dst()
          if otdst is None:
!             raise ValueError("astimezone():  utcoffset() returned a duration "
                               "but dst() returned None")
!         total_added_to_other = otoff - otdst - myoff
!         if total_added_to_other:
!             other += total_added_to_other
!             otoff = other.utcoffset()
!             if otoff is None:
!                 self._inconsistent_utcoffset_error()
!         # The distance now from self to other is
!         # self - other == naive(self) - myoff - (naive(other) - otoff) ==
!         # naive(self) - myoff -
!         #             ((naive(self) + total_added_to_other - otoff) ==
!         # - myoff - total_added_to_other + otoff
!         delta = otoff - myoff - total_added_to_other
!         ##assert (other == self) == (not delta) # expensive
!         if not delta:
              return other
  
!         # Must have crossed a DST switch point.
!         total_added_to_other += delta
!         other += delta
!         otoff = other.utcoffset()
!         if otoff is None:
!             self._inconsistent_utcoffset_error()
!         ##assert (other == self) == (otoff - myoff == total_added_to_other)
!         if otoff - myoff == total_added_to_other:
!             return other
!         raise ValueError("astimezone():  the source datetimetz can't be "
!                          "expressed in the target timezone's local time")
  
      def isoformat(self, sep='T'):
--- 1641,1675 ----
          # See the long comment block at the end of this file for an
          # explanation of this algorithm.  That it always works requires a
!         # pretty intricate proof.  There are many equivalent ways to code
!         # up the proof as an algorithm.  This way favors calling dst() over
!         # calling utcoffset(), because "the usual" utcoffset() calls dst()
!         # itself, and calling the latter instead saves a Python-level
!         # function call.  This way of coding it also follow the proof
!         # closely, w/ x=self, y=other, z=other, and z'=another.
          otdst = other.dst()
          if otdst is None:
!             raise ValueError("astimezone(): utcoffset() returned a duration "
                               "but dst() returned None")
!         delta = otoff - otdst - myoff
!         if delta:
!             other += delta
!             otdst = other.dst()
!             if otdst is None:
!                 self._inconsistent_dst()
!         if not otdst:
              return other
  
!         another = other + otdst
!         anotherdst = another.dst()
!         if anotherdst is None:
!             self._inconsistent_dst()
! 
!         if otdst == anotherdst:
!             other = another
!         else:
!             # This is the "unspellable hour" case, and we *don't* want
!             # the DST spelling here.
!             pass
!         return other
  
      def isoformat(self, sep='T'):

Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.94
retrieving revision 1.95
diff -C2 -d -r1.94 -r1.95
*** test_datetime.py	2 Jan 2003 21:01:22 -0000	1.94
--- test_datetime.py	4 Jan 2003 04:50:16 -0000	1.95
***************
*** 2607,2611 ****
  # For better test coverage, we want another flavor of UTC that's west of
  # the Eastern and Pacific timezones.
! utc_fake = FixedOffset(-12, "UTCfake", 0)
  
  class TestTimezoneConversions(unittest.TestCase):
--- 2607,2611 ----
  # For better test coverage, we want another flavor of UTC that's west of
  # the Eastern and Pacific timezones.
! utc_fake = FixedOffset(-12*60, "UTCfake", 0)
  
  class TestTimezoneConversions(unittest.TestCase):
***************
*** 2658,2680 ****
          # 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.
--- 2658,2672 ----
          # 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.
          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
!             # is ineffable.  We want the conversion back to repeat 1:MM.
!             expected_diff = ZERO
          else:
!             expected_diff = HOUR
!         self.assertEqual(nexthour_tz - dt, expected_diff)
  
      # Check a time that's outside DST.
***************
*** 2753,2756 ****
--- 2745,2773 ----
          got = sixutc.astimezone(Eastern).astimezone(None)
          self.assertEqual(expected, got)
+ 
+         # Now on the day DST ends, we want "repeat an hour" behavior.
+         #  UTC  4:MM  5:MM  6:MM  7:MM  checking these
+         #  EST 23:MM  0:MM  1:MM  2:MM
+         #  EDT  0:MM  1:MM  2:MM  3:MM
+         # 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
+                 # Convert that to UTC.
+                 first_std_hour -= tz.utcoffset(None)
+                 # Adjust for possibly fake UTC.
+                 asutc = first_std_hour + utc.utcoffset(None)
+                 # First UTC hour to convert; this is 4:00 when utc=utc_real &
+                 # tz=Eastern.
+                 asutcbase = asutc.replace(tzinfo=utc)
+                 for tzhour in (0, 1, 1, 2):
+                     expectedbase = self.dstoff.replace(hour=tzhour)
+                     for minute in 0, 30, 59:
+                         expected = expectedbase.replace(minute=minute)
+                         asutc = asutcbase.replace(minute=minute)
+                         astz = asutc.astimezone(tz)
+                         self.assertEqual(astz.replace(tzinfo=None), expected)
+                     asutcbase += HOUR
+ 
  
      def test_bogus_dst(self):