diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -901,13 +901,21 @@ *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 @@ * 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 @@ 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 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1065,13 +1065,13 @@ 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 @@ 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 @@ (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 @@ 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 @@ 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 @@ 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 @@ 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 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2544,7 +2544,7 @@ 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 @@ 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 @@ 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 @@ 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 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -24,6 +24,9 @@ Library ------- +- Issue #15006: Allow equality comparison between naive and aware + time or datetime objects. + - Issue #15036: Allow removing or changing multiple items in single-file mailboxes (mbox, MMDF, Babyl) flushing the mailbox between the changes. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3707,6 +3707,14 @@ 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 @@ 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 "