From 0831382d69890eeab58f43588887058a52a1f4b5 Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Fri, 15 Jun 2012 20:19:47 -0400 Subject: [PATCH] Issue #15006: Allow equality comparison between naive and aware time or datetime objects. --- Doc/library/datetime.rst | 20 ++++++++++++++++++-- Lib/datetime.py | 22 ++++++++++++++-------- Lib/test/datetimetester.py | 12 ++++++------ Misc/NEWS | 5 ++--- Modules/_datetimemodule.c | 16 ++++++++++++++++ 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 72e35df075d..5c2d3d9d818 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -901,13 +901,21 @@ Supported operations: *datetime1* is considered less than *datetime2* when *datetime1* precedes *datetime2* in time. - If one comparand is naive and the other is aware, :exc:`TypeError` is raised. + If one comparand is naive and the other is aware, :exc:`TypeError` + is raised if an order comparison is attempted. For equality + comparisons, naive instances are never equal to aware instances. + If both comparands are aware, and have the same :attr:`tzinfo` attribute, the common :attr:`tzinfo` attribute is ignored and the base datetimes are compared. If both comparands are aware and have different :attr:`tzinfo` attributes, the comparands are first adjusted by subtracting their UTC offsets (obtained from ``self.utcoffset()``). + .. versionchanged:: 3.3 + + Equality comparisons between naive and aware :class:`datetime` + instances don't raise :exc:`TypeError`. + .. note:: In order to stop comparison from falling back to the default scheme of comparing @@ -1316,7 +1324,10 @@ Supported operations: * comparison of :class:`.time` to :class:`.time`, where *a* is considered less than *b* when *a* precedes *b* in time. If one comparand is naive and the other - is aware, :exc:`TypeError` is raised. If both comparands are aware, and have + is aware, :exc:`TypeError` is raised if an order comparison is attempted. For equality + comparisons, naive instances are never equal to aware instances. + + If both comparands are aware, and have the same :attr:`tzinfo` attribute, the common :attr:`tzinfo` attribute is ignored and the base times are compared. If both comparands are aware and have different :attr:`tzinfo` attributes, the comparands are first adjusted by @@ -1326,6 +1337,11 @@ Supported operations: different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return :const:`False` or :const:`True`, respectively. + .. versionchanged:: 3.3 + + Equality comparisons between naive and aware :class:`time` instances + don't raise :exc:`TypeError`. + * hash, use as dict key * efficient pickling diff --git a/Lib/datetime.py b/Lib/datetime.py index 21aab35604c..6ab2499f4b1 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1065,13 +1065,13 @@ class time: def __eq__(self, other): if isinstance(other, time): - return self._cmp(other) == 0 + return self._cmp(other, allow_mixed=True) == 0 else: return False def __ne__(self, other): if isinstance(other, time): - return self._cmp(other) != 0 + return self._cmp(other, allow_mixed=True) != 0 else: return True @@ -1099,7 +1099,7 @@ class time: else: _cmperror(self, other) - def _cmp(self, other): + def _cmp(self, other, allow_mixed=False): assert isinstance(other, time) mytz = self._tzinfo ottz = other._tzinfo @@ -1118,7 +1118,10 @@ class time: (other._hour, other._minute, other._second, other._microsecond)) if myoff is None or otoff is None: - raise TypeError("cannot compare naive and aware times") + if allow_mixed: + return 2 # arbitrary non-zero value + else: + raise TypeError("cannot compare naive and aware times") myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1) othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1) return _cmp((myhhmm, self._second, self._microsecond), @@ -1615,7 +1618,7 @@ class datetime(date): def __eq__(self, other): if isinstance(other, datetime): - return self._cmp(other) == 0 + return self._cmp(other, allow_mixed=True) == 0 elif not isinstance(other, date): return NotImplemented else: @@ -1623,7 +1626,7 @@ class datetime(date): def __ne__(self, other): if isinstance(other, datetime): - return self._cmp(other) != 0 + return self._cmp(other, allow_mixed=True) != 0 elif not isinstance(other, date): return NotImplemented else: @@ -1661,7 +1664,7 @@ class datetime(date): else: _cmperror(self, other) - def _cmp(self, other): + def _cmp(self, other, allow_mixed=False): assert isinstance(other, datetime) mytz = self._tzinfo ottz = other._tzinfo @@ -1682,7 +1685,10 @@ class datetime(date): other._hour, other._minute, other._second, other._microsecond)) if myoff is None or otoff is None: - raise TypeError("cannot compare naive and aware datetimes") + if allow_mixed: + return 2 # arbitrary non-zero value + else: + raise TypeError("cannot compare naive and aware datetimes") # XXX What follows could be done more efficiently... diff = self - other # this will take offsets into account if diff.days < 0: diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index a4180ae69c8..048d63cdd13 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2544,7 +2544,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): self.assertEqual(t1, t2) self.assertEqual(t1, t3) self.assertEqual(t2, t3) - self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive + self.assertNotEqual(t4, t5) # mixed tz-aware & naive self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive @@ -2696,7 +2696,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): t2 = t2.replace(tzinfo=FixedOffset(None, "")) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) + self.assertNotEqual(t1, t2) # In time w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): @@ -2801,16 +2801,16 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): microsecond=1) self.assertTrue(t1 > t2) - # Make t2 naive and it should fail. + # Make t2 naive and it should differ. t2 = self.theclass.min - self.assertRaises(TypeError, lambda: t1 == t2) + self.assertNotEqual(t1, t2) self.assertEqual(t2, t2) # It's also naive if it has tzinfo but tzinfo.utcoffset() is None. class Naive(tzinfo): def utcoffset(self, dt): return None t2 = self.theclass(5, 6, 7, tzinfo=Naive()) - self.assertRaises(TypeError, lambda: t1 == t2) + self.assertNotEqual(t1, t2) self.assertEqual(t2, t2) # OTOH, it's OK to compare two of these mixing the two ways of being @@ -3327,7 +3327,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): t2 = t2.replace(tzinfo=FixedOffset(None, "")) self.assertEqual(t1, t2) t2 = t2.replace(tzinfo=FixedOffset(0, "")) - self.assertRaises(TypeError, lambda: t1 == t2) + self.assertNotEqual(t1, t2) # In datetime w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): diff --git a/Misc/NEWS b/Misc/NEWS index 9e7df250d26..7e20d1bc78a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -24,9 +24,8 @@ Core and Builtins Library ------- -- Issue #14938: importlib.abc.SourceLoader.is_package() will not consider a - module whose name ends in '__init__' a package (e.g. importing pkg.__init__ - directly should be considered a module, not a package). +- Issue #15006: Allow equality comparison between naive and aware + time or datetime objects. - Issue #14982: Document that pkgutil's iteration functions require the non-standard iter_modules() method to be defined by an importer (something diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 31501242d1a..d3a502db91f 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3707,6 +3707,14 @@ time_richcompare(PyObject *self, PyObject *other, int op) TIME_GET_MICROSECOND(other); result = diff_to_bool(diff, op); } + else if (op == Py_EQ) { + result = Py_False; + Py_INCREF(result); + } + else if (op == Py_NE) { + result = Py_True; + Py_INCREF(result); + } else { PyErr_SetString(PyExc_TypeError, "can't compare offset-naive and " @@ -4584,6 +4592,14 @@ datetime_richcompare(PyObject *self, PyObject *other, int op) Py_DECREF(delta); result = diff_to_bool(diff, op); } + else if (op == Py_EQ) { + result = Py_False; + Py_INCREF(result); + } + else if (op == Py_NE) { + result = Py_True; + Py_INCREF(result); + } else { PyErr_SetString(PyExc_TypeError, "can't compare offset-naive and "