Issue #15006: Allow equality comparison between naive and aware time
or datetime objects.
This commit is contained in:
parent
ea0b823940
commit
0831382d69
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 "
|
||||
|
|
Loading…
Reference in New Issue