summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libdatetime.tex16
-rw-r--r--Lib/test/test_datetime.py38
-rw-r--r--Misc/NEWS8
-rw-r--r--Modules/datetimemodule.c14
4 files changed, 75 insertions, 1 deletions
diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex
index 058d6d5..693ae79 100644
--- a/Doc/lib/libdatetime.tex
+++ b/Doc/lib/libdatetime.tex
@@ -381,6 +381,14 @@ Supported operations:
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
@@ -711,6 +719,14 @@ Supported operations:
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
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index 0b9597a..995b6a0 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -880,6 +880,44 @@ class TestDate(unittest.TestCase):
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)
diff --git a/Misc/NEWS b/Misc/NEWS
index ac61241..d63d522 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -100,6 +100,14 @@ Extension modules
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
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 6f0cf1e..31e006d 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -2437,8 +2437,15 @@ date_richcompare(PyDateTime_Date *self, PyObject *other, int op)
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;
}
@@ -4018,6 +4025,11 @@ datetime_richcompare(PyDateTime_DateTime *self, PyObject *other, int op)
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'",