[Zope3-checkins] CVS: Zope3/src/datetime - _datetime.py:1.5 doc.txt:1.3

Tim Peters tim.one@comcast.net
Thu, 26 Dec 2002 19:16:20 -0500


Update of /cvs-repository/Zope3/src/datetime
In directory cvs.zope.org:/tmp/cvs-serv29682/src/datetime

Modified Files:
	_datetime.py doc.txt 
Log Message:
Brrrr.  Many changes to make comparison and subtraction of aware objects
ignore tzinfo if the operands have identical tzinfo members (meaning
object identity -- "is").  I misunderstood the intent here, reading
a wrong conclusion into conflicting clues.

The Python implementation's datetime.__cmp__ now handles all comparisons
for datetime and datetimetz, and likewise for time.__cmp__ wrt timetz.
The C implementation does that already.  The problem was that, e.g.,
comparing a naive time to an aware timetz failed to raise TypeError,
as previously time.__cmp__ silently ignored that "other" was actually
of type timetz and just compared the common base fields.  Unfortunately,
no test case caught this, so added one.  Unfortunately again, the new
test has to be skipped when running the Python implementation under 2.2.2,
because it triggers a Python bug (which Guido already fixed in 2.2 CVS
for Python 2.2.3).


=== Zope3/src/datetime/_datetime.py 1.4 => 1.5 ===
--- Zope3/src/datetime/_datetime.py:1.4	Wed Dec 25 23:49:46 2002
+++ Zope3/src/datetime/_datetime.py	Thu Dec 26 19:15:49 2002
@@ -892,13 +892,38 @@
 
     def __cmp__(self, other):
         """Three-way comparison."""
-        if isinstance(other, time):
+        if not isinstance(other, time):
+            # XXX Buggy in 2.2.2.
+            raise TypeError("can't compare '%s' to '%s'" % (
+                            type(self).__name__, type(other).__name__))
+        mytz = myoff = None
+        if isinstance(self, timetz):
+            mytz = self._tzinfo
+        ottz = otoff = None
+        if isinstance(other, timetz):
+            ottz = other._tzinfo
+
+        if mytz is ottz:
+            base_compare = True
+        else:
+            if isinstance(self, timetz):
+                myoff = self._utcoffset()
+            if isinstance(other, timetz):
+                otoff = other._utcoffset()
+            base_compare = myoff == otoff
+
+        if base_compare:
             return cmp((self.__hour, self.__minute, self.__second,
                         self.__microsecond),
                        (other.__hour, other.__minute, other.__second,
                         other.__microsecond))
-        raise TypeError, ("can't compare time to %s instance" %
-                          type(other).__name__)
+        if myoff is None or otoff is None:
+            # XXX Buggy in 2.2.2.
+            raise TypeError("cannot compare naive and aware times")
+        myhhmm = self.__hour * 60 + self.__minute - myoff
+        othhmm = other.__hour * 60 + other.__minute - otoff
+        return cmp((myhhmm, self.__second, self.__microsecond),
+                   (othhmm, other.__second, other.__microsecond))
 
     def __hash__(self):
         """Hash."""
@@ -1042,35 +1067,17 @@
         """
         _check_tzinfo_arg(tzinfo)
         super(timetz, self).__init__(hour, minute, second, microsecond)
-        self.__tzinfo = tzinfo
+        self._tzinfo = tzinfo
 
     # Read-only field accessors
-    tzinfo = property(lambda self: self.__tzinfo, doc="timezone info object")
+    tzinfo = property(lambda self: self._tzinfo, doc="timezone info object")
 
-    # Standard conversions, __cmp__, __hash__ (and helpers)
-
-    def __cmp__(self, other):
-        """Three-way comparison."""
-        if not isinstance(other, time):
-            raise TypeError("can't compare timetz to %s instance" %
-                            type(other).__name__)
-        superself = super(timetz, self)
-        supercmp = superself.__cmp__
-        myoff = self._utcoffset()
-        otoff = other._utcoffset()
-        if myoff == otoff:
-            return supercmp(other)
-        if myoff is None or otoff is None:
-            raise TypeError, "cannot mix naive and timezone-aware time"
-        myhhmm = self.hour * 60 + self.minute - myoff
-        othhmm = other.hour * 60 + other.minute - otoff
-        return cmp((myhhmm, self.second, self.microsecond),
-                   (othhmm, other.second, other.microsecond))
+    # Standard conversions, __hash__ (and helpers)
 
     def __hash__(self):
         """Hash."""
         tzoff = self._utcoffset()
-        if not tzoff: # zero or None!
+        if not tzoff: # zero or None
             return super(timetz, self).__hash__()
         h, m = divmod(self.hour * 60 + self.minute - tzoff, 60)
         if 0 <= h < 24:
@@ -1098,9 +1105,9 @@
     def __repr__(self):
         """Convert to formal string, for repr()."""
         s = super(timetz, self).__repr__()
-        if self.__tzinfo is not None:
+        if self._tzinfo is not None:
             assert s[-1:] == ")"
-            s = s[:-1] + ", tzinfo=%r" % self.__tzinfo + ")"
+            s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
         return s
 
     def isoformat(self):
@@ -1121,7 +1128,7 @@
     def utcoffset(self):
         """Return the timezone offset in minutes east of UTC (negative west of
         UTC)."""
-        offset = _call_tzinfo_method(self, self.__tzinfo, "utcoffset")
+        offset = _call_tzinfo_method(self, self._tzinfo, "utcoffset")
         offset = _check_utc_offset("utcoffset", offset)
         if offset is not None:
             offset = timedelta(minutes=offset)
@@ -1129,7 +1136,7 @@
 
     # Return an integer (or None) instead of a timedelta (or None).
     def _utcoffset(self):
-        offset = _call_tzinfo_method(self, self.__tzinfo, "utcoffset")
+        offset = _call_tzinfo_method(self, self._tzinfo, "utcoffset")
         offset = _check_utc_offset("utcoffset", offset)
         return offset
 
@@ -1140,7 +1147,7 @@
         it mean anything in particular. For example, "GMT", "UTC", "-500",
         "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
         """
-        name = _call_tzinfo_method(self, self.__tzinfo, "tzname")
+        name = _call_tzinfo_method(self, self._tzinfo, "tzname")
         _check_tzname(name)
         return name
 
@@ -1153,7 +1160,7 @@
         need to consult dst() unless you're interested in displaying the DST
         info.
         """
-        offset = _call_tzinfo_method(self, self.__tzinfo, "dst")
+        offset = _call_tzinfo_method(self, self._tzinfo, "dst")
         offset = _check_utc_offset("dst", offset)
         if offset is not None:
             offset = timedelta(minutes=offset)
@@ -1178,7 +1185,7 @@
 
     # Return an integer (or None) instead of a timedelta (or None).
     def _dst(self):
-        offset = _call_tzinfo_method(self, self.__tzinfo, "dst")
+        offset = _call_tzinfo_method(self, self._tzinfo, "dst")
         offset = _check_utc_offset("dst", offset)
         return offset
 
@@ -1192,10 +1199,10 @@
 
     def __getstate__(self):
         basestate = time.__getstate__(self)
-        if self.__tzinfo is None:
+        if self._tzinfo is None:
             return (basestate,)
         else:
-            return (basestate, self.__tzinfo)
+            return (basestate, self._tzinfo)
 
     def __setstate__(self, state):
         if not isinstance(state, tuple):
@@ -1205,9 +1212,9 @@
                             "2-tuple argument")
         time.__setstate__(self, state[0])
         if len(state) == 1:
-            self.__tzinfo = None
+            self._tzinfo = None
         else:
-            self.__tzinfo = state[1]
+            self._tzinfo = state[1]
 
 timetz.min = timetz(0, 0, 0)
 timetz.max = timetz(23, 59, 59, 999999)
@@ -1373,16 +1380,45 @@
         return temp.replace(tzinfo=tz)
 
     def __cmp__(self, other):
-        "Three-way comparison."
-        if isinstance(other, datetime):
+        import datetime
+        if not isinstance(other, datetime.datetime):
+            # XXX Buggy in 2.2.2.
+            raise TypeError("can't compare '%s' to '%s'" % (
+                            type(self).__name__, type(other).__name__))
+        mytz = myoff = None
+        if isinstance(self, datetimetz):
+            mytz = self._tzinfo
+        ottz = otoff = None
+        if isinstance(other, datetimetz):
+            ottz = other._tzinfo
+
+        if mytz is ottz:
+            base_compare = True
+        else:
+            if isinstance(self, datetimetz):
+                myoff = self._utcoffset()
+            if isinstance(other, datetimetz):
+                otoff = other._utcoffset()
+            base_compare = myoff == otoff
+
+        if base_compare:
             return cmp((self.__year, self.__month, self.__day,
                         self.__hour, self.__minute, self.__second,
                         self.__microsecond),
                        (other.__year, other.__month, other.__day,
                         other.__hour, other.__minute, other.__second,
                         other.__microsecond))
-        raise TypeError, ("can't compare datetime to %s instance" %
-                          type(other).__name__)
+        if myoff is None or otoff is None:
+            # XXX Buggy in 2.2.2.
+            raise TypeError("cannot compare naive and aware datetimes")
+        # XXX What follows could be done more efficiently...
+        diff = (datetime.datetime.__sub__(self, other) +
+                timedelta(minutes=otoff-myoff))
+        if diff.days < 0:
+            return -1
+        if diff == timedelta():
+            return 0
+        return 1
 
     def __hash__(self):
         "Hash."
@@ -1497,9 +1533,9 @@
         _check_tzinfo_arg(tzinfo)
         super(datetimetz, self).__init__(year, month, day,
                                          hour, minute, second, microsecond)
-        self.__tzinfo = tzinfo
+        self._tzinfo = tzinfo
 
-    tzinfo = property(lambda self: self.__tzinfo, doc="timezone info object")
+    tzinfo = property(lambda self: self._tzinfo, doc="timezone info object")
 
     def fromtimestamp(cls, t, tzinfo=None):
         """Construct a datetimetz from a POSIX timestamp (like time.time()).
@@ -1549,7 +1585,7 @@
     def timetz(self):
         "Return the time part."
         return timetz(self.hour, self.minute, self.second, self.microsecond,
-                      self.__tzinfo)
+                      self._tzinfo)
 
     def replace(self, year=None, month=None, day=None, hour=None,
                 minute=None, second=None, microsecond=None, tzinfo=True):
@@ -1603,15 +1639,15 @@
 
     def __repr__(self):
         s = super(datetimetz, self).__repr__()
-        if self.__tzinfo is not None:
+        if self._tzinfo is not None:
             assert s[-1:] == ")"
-            s = s[:-1] + ", tzinfo=%r" % self.__tzinfo + ")"
+            s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
         return s
 
     def utcoffset(self):
         """Return the timezone offset in minutes east of UTC (negative west of
         UTC)."""
-        offset = _call_tzinfo_method(self, self.__tzinfo, "utcoffset")
+        offset = _call_tzinfo_method(self, self._tzinfo, "utcoffset")
         offset = _check_utc_offset("utcoffset", offset)
         if offset is not None:
             offset = timedelta(minutes=offset)
@@ -1619,7 +1655,7 @@
 
     # Return an integer (or None) instead of a timedelta (or None).
     def _utcoffset(self):
-        offset = _call_tzinfo_method(self, self.__tzinfo, "utcoffset")
+        offset = _call_tzinfo_method(self, self._tzinfo, "utcoffset")
         offset = _check_utc_offset("utcoffset", offset)
         return offset
 
@@ -1630,7 +1666,7 @@
         it mean anything in particular. For example, "GMT", "UTC", "-500",
         "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
         """
-        name = _call_tzinfo_method(self, self.__tzinfo, "tzname")
+        name = _call_tzinfo_method(self, self._tzinfo, "tzname")
         _check_tzname(name)
         return name
 
@@ -1643,7 +1679,7 @@
         need to consult dst() unless you're interested in displaying the DST
         info.
         """
-        offset = _call_tzinfo_method(self, self.__tzinfo, "dst")
+        offset = _call_tzinfo_method(self, self._tzinfo, "dst")
         offset = _check_utc_offset("dst", offset)
         if offset is not None:
             offset = timedelta(minutes=offset)
@@ -1651,14 +1687,14 @@
 
     # Return an integer (or None) instead of a timedelta (or None).1573
     def _dst(self):
-        offset = _call_tzinfo_method(self, self.__tzinfo, "dst")
+        offset = _call_tzinfo_method(self, self._tzinfo, "dst")
         offset = _check_utc_offset("dst", offset)
         return offset
 
     def __add__(self, other):
         result = super(datetimetz, self).__add__(other)
         assert isinstance(result, datetimetz)
-        result.__tzinfo = self.__tzinfo
+        result._tzinfo = self._tzinfo
         return result
 
     __radd__ = __add__
@@ -1671,35 +1707,22 @@
             # self + (-timedelta) by datetime.__sub__, and the latter is
             # handled by datetimetz.__add__.
             return supersub(other)
-        myoff = self._utcoffset()
-        otoff = other._utcoffset()
-        if myoff == otoff:
+        mytz = self._tzinfo
+        ottz = None
+        if isinstance(other, datetimetz):
+            ottz = other._tzinfo
+        if mytz is ottz:
             return supersub(other)
-        if myoff is None or otoff is None:
-            raise TypeError, "cannot mix naive and timezone-aware time"
-        return supersub(other) + timedelta(minutes=otoff-myoff)
 
-    def __cmp__(self, other):
-        if not isinstance(other, datetime):
-            raise TypeError("can't compare datetime to %s instance" %
-                            type(other).__name__)
-        superself = super(datetimetz, self)
-        supercmp = superself.__cmp__
         myoff = self._utcoffset()
         otoff = None
         if isinstance(other, datetimetz):
             otoff = other._utcoffset()
         if myoff == otoff:
-            return supercmp(other)
+            return supersub(other)
         if myoff is None or otoff is None:
             raise TypeError, "cannot mix naive and timezone-aware time"
-        # XXX What follows could be done more efficiently...
-        diff = superself.__sub__(other) + timedelta(minutes=otoff-myoff)
-        if diff.days < 0:
-            return -1
-        if diff == timedelta():
-            return 0
-        return 1
+        return supersub(other) + timedelta(minutes=otoff-myoff)
 
     def __hash__(self):
         tzoff = self._utcoffset()
@@ -1711,10 +1734,10 @@
 
     def __getstate__(self):
         basestate = datetime.__getstate__(self)
-        if self.__tzinfo is None:
+        if self._tzinfo is None:
             return (basestate,)
         else:
-            return (basestate, self.__tzinfo)
+            return (basestate, self._tzinfo)
 
     def __setstate__(self, state):
         if not isinstance(state, tuple):
@@ -1724,9 +1747,9 @@
                             "2-tuple argument")
         datetime.__setstate__(self, state[0])
         if len(state) == 1:
-            self.__tzinfo = None
+            self._tzinfo = None
         else:
-            self.__tzinfo = state[1]
+            self._tzinfo = state[1]
 
 
 datetimetz.min = datetimetz(1, 1, 1)


=== Zope3/src/datetime/doc.txt 1.2 => 1.3 ===
--- Zope3/src/datetime/doc.txt:1.2	Wed Dec 25 09:12:11 2002
+++ Zope3/src/datetime/doc.txt	Thu Dec 26 19:15:49 2002
@@ -919,10 +919,14 @@
 
 Supported operations:
 
-    - comparison of timetz to timetz, where timetz1 is considered
-      less than timetz2 when timetz1 precedes timetz2 in time, and
-      where the timetz objects are first adjusted by subtracting
-      their UTC offsets (obtained from self.utcoffset()).
+    - comparison of timetz to time or timetz, where a is considered
+      less than b when a precedes b in time.  If one comparand is
+      naive and the other is aware, TypeError is raised.  If both
+      comparands are aware, and have the same tzinfo member, the
+      common tzinfo member is ignored and the base times are compared.
+      If both comparands are aware and have different tzinfo members,
+      the comparands are first adjusted by subtracting their UTC
+      offsets (obtained from self.utcoffset()).
 
     - hash, use as dict key
 
@@ -1052,8 +1056,8 @@
       datetimetz2.tzinfo is set to datetimetz1.tzinfo.
 
     - datetimetz1 - timedelta -> datetimetz2
-      The same as addition of datetime objects, except that
-      datetimetz2.tzinfo is set to datetimetz1.tzinfo.
+      The same as subtraction of timedelta from datetime objects, except
+      that datetimetz2.tzinfo is set to datetimetz1.tzinfo.
 
     - aware_datetimetz1 - aware_datetimetz2 -> timedelta
       naive_datetimetz1 - naive_datetimetz2 -> timedelta
@@ -1065,22 +1069,23 @@
       both are aware.  If one is aware and the other is naive, TypeError
       is raised.
 
-      If both are naive, subtraction acts as for datetime subtraction.
+      If both are naive, or both are aware and have the same tzinfo
+      member, subtraction acts as for datetime subtraction.
 
-      If both are aware datetimetz objects, a-b acts as if a and b were
-      first converted to UTC datetimes (by subtracting a.utcoffset()
-      minutes from a, and b.utcoffset() minutes from b), and then doing
-      datetime subtraction, except that the implementation never
-      overflows.
-
-    - Comparison of datetimetz to datetime or datetimetz.  As for
-      subtraction, comparison is defined only if both operands are
-      naive or both are aware.  If both are naive, comparison is as
-      for datetime objects with the same date and time components.
-      If both are aware, comparison acts as if both were converted to
-      UTC datetimes first, except the the implementation never
-      overflows.  If one comparand is naive and the other aware,
-      TypeError is raised.
+      If both are aware and have different tzinfo members, a-b acts as
+      if a and b were first converted to UTC datetimes (by subtracting
+      a.utcoffset() minutes from a, and b.utcoffset() minutes from b),
+      and then doing datetime subtraction, except that the implementation
+      never  overflows.
+
+    - Comparison of datetimetz to datetime or datetimetz, where a is
+      considered less than b when a precedes b in time.  If one
+      comparand is naive and the other is aware, TypeError is raised.
+      If both comparands are aware, and have the same tzinfo member,
+      the common tzinfo member is ignored and the base datetimes
+      are compared.  If both comparands are aware and have different
+      tzinfo members, the comparands are first adjusted by subtracting
+      their UTC offsets (obtained from self.utcoffset()).
 
     - hash, use as dict key