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
|
*datetime1* is considered less than *datetime2* when *datetime1* precedes
|
||||||
*datetime2* in time.
|
*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
|
If both comparands are aware, and have the same :attr:`tzinfo` attribute, the
|
||||||
common :attr:`tzinfo` attribute is ignored and the base datetimes are
|
common :attr:`tzinfo` attribute is ignored and the base datetimes are
|
||||||
compared. If both comparands are aware and have different :attr:`tzinfo`
|
compared. If both comparands are aware and have different :attr:`tzinfo`
|
||||||
attributes, the comparands are first adjusted by subtracting their UTC
|
attributes, the comparands are first adjusted by subtracting their UTC
|
||||||
offsets (obtained from ``self.utcoffset()``).
|
offsets (obtained from ``self.utcoffset()``).
|
||||||
|
|
||||||
|
.. versionchanged:: 3.3
|
||||||
|
|
||||||
|
Equality comparisons between naive and aware :class:`datetime`
|
||||||
|
instances don't raise :exc:`TypeError`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
In order to stop comparison from falling back to the default scheme of comparing
|
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
|
* 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
|
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
|
the same :attr:`tzinfo` attribute, the common :attr:`tzinfo` attribute is
|
||||||
ignored and the base times are compared. If both comparands are aware and
|
ignored and the base times are compared. If both comparands are aware and
|
||||||
have different :attr:`tzinfo` attributes, the comparands are first adjusted by
|
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
|
different type, :exc:`TypeError` is raised unless the comparison is ``==`` or
|
||||||
``!=``. The latter cases return :const:`False` or :const:`True`, respectively.
|
``!=``. 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
|
* hash, use as dict key
|
||||||
|
|
||||||
* efficient pickling
|
* efficient pickling
|
||||||
|
|
|
@ -1065,13 +1065,13 @@ class time:
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, time):
|
if isinstance(other, time):
|
||||||
return self._cmp(other) == 0
|
return self._cmp(other, allow_mixed=True) == 0
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
if isinstance(other, time):
|
if isinstance(other, time):
|
||||||
return self._cmp(other) != 0
|
return self._cmp(other, allow_mixed=True) != 0
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1099,7 +1099,7 @@ class time:
|
||||||
else:
|
else:
|
||||||
_cmperror(self, other)
|
_cmperror(self, other)
|
||||||
|
|
||||||
def _cmp(self, other):
|
def _cmp(self, other, allow_mixed=False):
|
||||||
assert isinstance(other, time)
|
assert isinstance(other, time)
|
||||||
mytz = self._tzinfo
|
mytz = self._tzinfo
|
||||||
ottz = other._tzinfo
|
ottz = other._tzinfo
|
||||||
|
@ -1118,7 +1118,10 @@ class time:
|
||||||
(other._hour, other._minute, other._second,
|
(other._hour, other._minute, other._second,
|
||||||
other._microsecond))
|
other._microsecond))
|
||||||
if myoff is None or otoff is None:
|
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)
|
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
|
||||||
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
|
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
|
||||||
return _cmp((myhhmm, self._second, self._microsecond),
|
return _cmp((myhhmm, self._second, self._microsecond),
|
||||||
|
@ -1615,7 +1618,7 @@ class datetime(date):
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, datetime):
|
if isinstance(other, datetime):
|
||||||
return self._cmp(other) == 0
|
return self._cmp(other, allow_mixed=True) == 0
|
||||||
elif not isinstance(other, date):
|
elif not isinstance(other, date):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
else:
|
else:
|
||||||
|
@ -1623,7 +1626,7 @@ class datetime(date):
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
if isinstance(other, datetime):
|
if isinstance(other, datetime):
|
||||||
return self._cmp(other) != 0
|
return self._cmp(other, allow_mixed=True) != 0
|
||||||
elif not isinstance(other, date):
|
elif not isinstance(other, date):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
else:
|
else:
|
||||||
|
@ -1661,7 +1664,7 @@ class datetime(date):
|
||||||
else:
|
else:
|
||||||
_cmperror(self, other)
|
_cmperror(self, other)
|
||||||
|
|
||||||
def _cmp(self, other):
|
def _cmp(self, other, allow_mixed=False):
|
||||||
assert isinstance(other, datetime)
|
assert isinstance(other, datetime)
|
||||||
mytz = self._tzinfo
|
mytz = self._tzinfo
|
||||||
ottz = other._tzinfo
|
ottz = other._tzinfo
|
||||||
|
@ -1682,7 +1685,10 @@ class datetime(date):
|
||||||
other._hour, other._minute, other._second,
|
other._hour, other._minute, other._second,
|
||||||
other._microsecond))
|
other._microsecond))
|
||||||
if myoff is None or otoff is None:
|
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...
|
# XXX What follows could be done more efficiently...
|
||||||
diff = self - other # this will take offsets into account
|
diff = self - other # this will take offsets into account
|
||||||
if diff.days < 0:
|
if diff.days < 0:
|
||||||
|
|
|
@ -2544,7 +2544,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
|
||||||
self.assertEqual(t1, t2)
|
self.assertEqual(t1, t2)
|
||||||
self.assertEqual(t1, t3)
|
self.assertEqual(t1, t3)
|
||||||
self.assertEqual(t2, 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: t4 < t5) # mixed tz-aware & naive
|
||||||
self.assertRaises(TypeError, lambda: t5 < t4) # 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, ""))
|
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
|
||||||
self.assertEqual(t1, t2)
|
self.assertEqual(t1, t2)
|
||||||
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
|
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.
|
# In time w/ identical tzinfo objects, utcoffset is ignored.
|
||||||
class Varies(tzinfo):
|
class Varies(tzinfo):
|
||||||
|
@ -2801,16 +2801,16 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
|
||||||
microsecond=1)
|
microsecond=1)
|
||||||
self.assertTrue(t1 > t2)
|
self.assertTrue(t1 > t2)
|
||||||
|
|
||||||
# Make t2 naive and it should fail.
|
# Make t2 naive and it should differ.
|
||||||
t2 = self.theclass.min
|
t2 = self.theclass.min
|
||||||
self.assertRaises(TypeError, lambda: t1 == t2)
|
self.assertNotEqual(t1, t2)
|
||||||
self.assertEqual(t2, t2)
|
self.assertEqual(t2, t2)
|
||||||
|
|
||||||
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
|
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
|
||||||
class Naive(tzinfo):
|
class Naive(tzinfo):
|
||||||
def utcoffset(self, dt): return None
|
def utcoffset(self, dt): return None
|
||||||
t2 = self.theclass(5, 6, 7, tzinfo=Naive())
|
t2 = self.theclass(5, 6, 7, tzinfo=Naive())
|
||||||
self.assertRaises(TypeError, lambda: t1 == t2)
|
self.assertNotEqual(t1, t2)
|
||||||
self.assertEqual(t2, t2)
|
self.assertEqual(t2, t2)
|
||||||
|
|
||||||
# OTOH, it's OK to compare two of these mixing the two ways of being
|
# 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, ""))
|
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
|
||||||
self.assertEqual(t1, t2)
|
self.assertEqual(t1, t2)
|
||||||
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
|
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.
|
# In datetime w/ identical tzinfo objects, utcoffset is ignored.
|
||||||
class Varies(tzinfo):
|
class Varies(tzinfo):
|
||||||
|
|
|
@ -24,9 +24,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
- Issue #14938: importlib.abc.SourceLoader.is_package() will not consider a
|
- Issue #15006: Allow equality comparison between naive and aware
|
||||||
module whose name ends in '__init__' a package (e.g. importing pkg.__init__
|
time or datetime objects.
|
||||||
directly should be considered a module, not a package).
|
|
||||||
|
|
||||||
- Issue #14982: Document that pkgutil's iteration functions require the
|
- Issue #14982: Document that pkgutil's iteration functions require the
|
||||||
non-standard iter_modules() method to be defined by an importer (something
|
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);
|
TIME_GET_MICROSECOND(other);
|
||||||
result = diff_to_bool(diff, op);
|
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 {
|
else {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"can't compare offset-naive and "
|
"can't compare offset-naive and "
|
||||||
|
@ -4584,6 +4592,14 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
|
||||||
Py_DECREF(delta);
|
Py_DECREF(delta);
|
||||||
result = diff_to_bool(diff, op);
|
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 {
|
else {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"can't compare offset-naive and "
|
"can't compare offset-naive and "
|
||||||
|
|
Loading…
Reference in New Issue