[Python-checkins] python/nondist/sandbox/datetime US.py,1.5,1.6 datetime.py,1.125,1.126 test_datetime.py,1.82,1.83

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Wed, 25 Dec 2002 20:46:36 -0800


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

Modified Files:
	US.py datetime.py test_datetime.py 
Log Message:
timetz comparison, and datetimetz subtraction:  reimplemented these to
work as documented.  They weren't calling utcoffset() if both operands
had the same tzinfo object.  That's fine if it so happens that the
shared tzinfo object returns a fixed offset (independent of operand),
but can give wrong results if that's not so, and the latter obtains in
a tzinfo subclass instance trying to model both standard and daylight
times.

This repairs some of the surprises in the sandbox US.py demo, but
not all of them.  In a way, it makes one part even more surprising:
because addition of datetimetz and timedelta doesn't do anything with
tzinfo except attach it to the result, it's possible to add, e.g.,
one second to a datetimetz, and then see the result of that minus the
original datetimetz span an hour.  This can happen at DST boundaries
(see US.py's doctest for concrete examples).


Index: US.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/US.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** US.py	26 Dec 2002 01:17:55 -0000	1.5
--- US.py	26 Dec 2002 04:46:34 -0000	1.6
***************
*** 131,146 ****
  Sun Apr  7 02:00:00 2002
  
! The difference is confusing.  2:00:00 doesn't exist on the naive clock (the
! naive clock leaps from 1:59:59 to 3:00:00), and is taken to be in DST, as a
! redundant spelling of 1:00:00 standard time.  So we actually expect b to be
! about an hour *before* a.  But subtraction doesn't do that:  because the
! tzinfo objects are identical, subtraction ignores them, and the difference
! comes out as positive one second.
  
! >>> print after - before
! 0:00:01
  
! OTOH, if we convert them to UTC and subtract, the difference is about
! an hour.
  
  >>> class UTC(tzinfo):
--- 131,142 ----
  Sun Apr  7 02:00:00 2002
  
! 2:00:00 doesn't exist on the naive clock (the naive clock leaps from 1:59:59
! to 3:00:00), and is taken to be in DST, as a redundant spelling of 1:00:00
! standard time.  So we actually expect b to be about an hour *before* a.
  
! >>> print -(after - before)
! 0:59:59
  
! Converting to UTC and subtracting, we should get the same thing.
  
  >>> class UTC(tzinfo):
***************
*** 175,183 ****
  The naive clock repeats the times in 1:HH:MM, so 1:59:59 was actually
  ambiguous, and resolved arbitrarily as being in DST.  2:00:00 is in standard
! time, and is actually about an hour later, but the tzinfo objects are the same
! so the offsets are again ignored by subtraction.
  
  >>> print after - before
! 0:00:01
  >>> print after.astimezone(utc) - before.astimezone(utc)
  1:00:01
--- 171,178 ----
  The naive clock repeats the times in 1:HH:MM, so 1:59:59 was actually
  ambiguous, and resolved arbitrarily as being in DST.  2:00:00 is in standard
! time, and is actually about an hour later.
  
  >>> print after - before
! 1:00:01
  >>> print after.astimezone(utc) - before.astimezone(utc)
  1:00:01

Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.125
retrieving revision 1.126
diff -C2 -d -r1.125 -r1.126
*** datetime.py	25 Dec 2002 19:52:25 -0000	1.125
--- datetime.py	26 Dec 2002 04:46:34 -0000	1.126
***************
*** 1057,1066 ****
          superself = super(timetz, self)
          supercmp = superself.__cmp__
-         mytz = self.__tzinfo
-         ottz = None
-         if isinstance(other, timetz):
-             ottz = other.__tzinfo
-         if mytz is ottz:
-             return supercmp(other)
          myoff = self._utcoffset()
          otoff = other._utcoffset()
--- 1057,1060 ----
***************
*** 1673,1682 ****
          supersub = super(datetimetz, self).__sub__
          if not isinstance(other, datetime):
!             return supersub(other) # XXX should set tzinfo on result
!         mytz = self.__tzinfo
!         ottz = None
!         if isinstance(other, datetimetz):
!             ottz = other.__tzinfo
!         if mytz is ottz:
              return supersub(other)
          myoff = self._utcoffset()
--- 1667,1674 ----
          supersub = super(datetimetz, self).__sub__
          if not isinstance(other, datetime):
!             # This manages to attach self.tzinfo to the result via a
!             # devious route:  self - timedelta is changed to
!             # self + (-timedelta) by datetime.__sub__, and the latter is
!             # handled by datetimetz.__add__.
              return supersub(other)
          myoff = self._utcoffset()

Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.82
retrieving revision 1.83
diff -C2 -d -r1.82 -r1.83
*** test_datetime.py	25 Dec 2002 19:52:25 -0000	1.82
--- test_datetime.py	26 Dec 2002 04:46:34 -0000	1.83
***************
*** 1667,1670 ****
--- 1667,1701 ----
          self.assertRaises(ValueError, t.dst)
  
+     def test_aware_compare(self):
+         cls = self.theclass
+ 
+         # Primarily trying to ensure that utcoffset() gets called even if
+         # the comparands have the same tzinfo member.  timetz comparison
+         # didn't used to do so, although datetimetz comparison did.
+         class OperandDependentOffset(tzinfo):
+             def utcoffset(self, t):
+                 if t.minute < 10:
+                     return t.minute # d0 and d1 equal after adjustment
+                 else:
+                     return 59       # d2 off in the weeds
+ 
+         base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
+         d0 = base.replace(minute=3)
+         d1 = base.replace(minute=9)
+         d2 = base.replace(minute=11)
+         for x in d0, d1, d2:
+             for y in d0, d1, d2:
+                 got = cmp(x, y)
+                 if (x is d0 or x is d1) and (y is d0 or y is d1):
+                     expected = 0
+                 elif x is y is d2:
+                     expected = 0
+                 elif x is d2:
+                     expected = -1
+                 else:
+                     assert y is d2
+                     expected = 1
+                 self.assertEqual(got, expected)
+ 
  
  class TestTimeTZ(TestTime, TZInfoBase):
***************
*** 1875,1878 ****
--- 1906,1910 ----
          self.assertRaises(ValueError, base.replace, microsecond=1000000)
  
+ 
  class TestDateTimeTZ(TestDateTime, TZInfoBase):
      theclass = datetimetz
***************
*** 2056,2061 ****
          now = self.theclass.now()
          tz55 = FixedOffset(-330, "west 5:30")
!         timeaware = timetz(now.hour, now.minute, now.second,
!                            now.microsecond, tzinfo=tz55)
          nowaware = self.theclass.combine(now.date(), timeaware)
          self.failUnless(nowaware.tzinfo is tz55)
--- 2088,2092 ----
          now = self.theclass.now()
          tz55 = FixedOffset(-330, "west 5:30")
!         timeaware = now.timetz().replace(tzinfo=tz55)
          nowaware = self.theclass.combine(now.date(), timeaware)
          self.failUnless(nowaware.tzinfo is tz55)
***************
*** 2093,2103 ****
          # Make up a random timezone.
          tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
!         # Attach it to nowawareplus -- this is clumsy.
!         nowawareplus = self.theclass.combine(nowawareplus.date(),
!                                              timetz(nowawareplus.hour,
!                                                     nowawareplus.minute,
!                                                     nowawareplus.second,
!                                                     nowawareplus.microsecond,
!                                                     tzinfo=tzr))
          self.failUnless(nowawareplus.tzinfo is tzr)
          # Make sure the difference takes the timezone adjustments into account.
--- 2124,2129 ----
          # Make up a random timezone.
          tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
!         # Attach it to nowawareplus.
!         nowawareplus = nowawareplus.replace(tzinfo=tzr)
          self.failUnless(nowawareplus.tzinfo is tzr)
          # Make sure the difference takes the timezone adjustments into account.
***************
*** 2372,2375 ****
--- 2398,2433 ----
          self.failUnless(got.tzinfo is expected.tzinfo)
          self.assertEqual(got, expected)
+ 
+     def test_aware_subtract(self):
+         cls = self.theclass
+ 
+         # Primarily trying to ensure that utcoffset() gets called even if
+         # the operands have the same tzinfo member.  Subtraction didn't
+         # used to do this, and it makes a difference for DST-aware tzinfo
+         # instances.
+         class OperandDependentOffset(tzinfo):
+             def utcoffset(self, t):
+                 if t.minute < 10:
+                     return t.minute # d0 and d1 equal after adjustment
+                 else:
+                     return 59       # d2 off in the weeds
+ 
+         base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
+         d0 = base.replace(minute=3)
+         d1 = base.replace(minute=9)
+         d2 = base.replace(minute=11)
+         for x in d0, d1, d2:
+             for y in d0, d1, d2:
+                 got = x - y
+                 if (x is d0 or x is d1) and (y is d0 or y is d1):
+                     expected = timedelta(0)
+                 elif x is y is d2:
+                     expected = timedelta(0)
+                 elif x is d2:
+                     expected = timedelta(minutes=(11-59)-0)
+                 else:
+                     assert y is d2
+                     expected = timedelta(minutes=0-(11-59))
+                 self.assertEqual(got, expected)