comparison of date to date, where date1 is considered less than
date2 when date1 precedes date2 in time. In other words,
date1 < date2 if and only if date1.toordinal() < date2.toordinal().
+ \note{In order to stop comparison from falling back to the default
+ scheme of comparing object addresses, date comparison
+ normally raises \exception{TypeError} if the other comparand
+ isn't also a \class{date} object. However, \code{NotImplemented}
+ is returned instead if the other comparand has a
+ \method{timetuple} attribute. This hook gives other kinds of
+ date objects a chance at implementing mixed-type comparison.}
+
\item
hash, use as dict key
are compared. If both comparands are aware and have different
\member{tzinfo} members, the comparands are first adjusted by
subtracting their UTC offsets (obtained from \code{self.utcoffset()}).
+ \note{In order to stop comparison from falling back to the default
+ scheme of comparing object addresses, datetime comparison
+ normally raises \exception{TypeError} if the other comparand
+ isn't also a \class{datetime} object. However,
+ \code{NotImplemented} is returned instead if the other comparand
+ has a \method{timetuple} attribute. This hook gives other
+ kinds of date objects a chance at implementing mixed-type
+ comparison.}
\item
hash, use as dict key
self.assertRaises(TypeError, lambda: badarg > t1)
self.assertRaises(TypeError, lambda: badarg >= t1)
+ def test_mixed_compare(self):
+ our = self.theclass(2000, 4, 5)
+ self.assertRaises(TypeError, cmp, our, 1)
+ self.assertRaises(TypeError, cmp, 1, our)
+
+ class AnotherDateTimeClass(object):
+ def __cmp__(self, other):
+ # Return "equal" so calling this can't be confused with
+ # compare-by-address (which never says "equal" for distinct
+ # objects).
+ return 0
+
+ # This still errors, because date and datetime comparison raise
+ # TypeError instead of NotImplemented when they don't know what to
+ # do, in order to stop comparison from falling back to the default
+ # compare-by-address.
+ their = AnotherDateTimeClass()
+ self.assertRaises(TypeError, cmp, our, their)
+ # Oops: The next stab raises TypeError in the C implementation,
+ # but not in the Python implementation of datetime. The difference
+ # is due to that the Python implementation defines __cmp__ but
+ # the C implementation defines tp_richcompare. This is more pain
+ # to fix than it's worth, so commenting out the test.
+ # self.assertEqual(cmp(their, our), 0)
+
+ # But date and datetime comparison return NotImplemented instead if the
+ # other object has a timetuple attr. This gives the other object a
+ # chance to do the comparison.
+ class Comparable(AnotherDateTimeClass):
+ def timetuple(self):
+ return ()
+
+ their = Comparable()
+ self.assertEqual(cmp(our, their), 0)
+ self.assertEqual(cmp(their, our), 0)
+ self.failUnless(our == their)
+ self.failUnless(their == our)
+
def test_bool(self):
# All dates are considered true.
self.failUnless(self.theclass.min)
useful behavior when the optional tinzo argument was specified. See
also SF bug report <http://www.python.org/sf/660872>.
+ date and datetime comparison: In order to prevent comparison from
+ falling back to the default compare-object-addresses strategy, these
+ raised TypeError whenever they didn't understand the other object type.
+ They still do, except when the other object has a "timetuple" attribute,
+ in which case they return NotImplemented now. This gives other
+ datetime objects (e.g., mxDateTime) a chance to intercept the
+ comparison.
+
The constructors building a datetime from a timestamp could raise
ValueError if the platform C localtime()/gmtime() inserted "leap
seconds". Leap seconds are ignored now. On such platforms, it's
int diff;
if (! PyDate_Check(other)) {
+ if (PyObject_HasAttrString(other, "timetuple")) {
+ /* A hook for other kinds of date objects. */
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+ /* Stop this from falling back to address comparison. */
PyErr_Format(PyExc_TypeError,
- "can't compare date to %s instance",
+ "can't compare '%s' to '%s'",
+ self->ob_type->tp_name,
other->ob_type->tp_name);
return NULL;
}
int offset1, offset2;
if (! PyDateTime_Check(other)) {
+ if (PyObject_HasAttrString(other, "timetuple")) {
+ /* A hook for other kinds of datetime objects. */
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
/* Stop this from falling back to address comparison. */
PyErr_Format(PyExc_TypeError,
"can't compare '%s' to '%s'",