The following was produced in Python 3.9.7.
I am well aware that the comparison between tz-aware and naive datetime
instances is not allowed in Python and raises a TypeError
. However, when testing for equality (with the ==
and !=
operators) this is actually not the case. In fact, the comparison always returns False
:
import datetime
import pytz
t_tz_aware = datetime.datetime(2020, 5, 23, tzinfo=pytz.UTC)
t_naive = datetime.datetime(2020, 5, 23)
# Prints 'False'.
print(t_tz_aware == t_naive)
# Raises TypeError: can't compare offset-naive and offset-aware datetimes.
print(t_tz_aware < t_naive)
I checked the source code of the datetime
library and the function for comparing datetime objects has a parameter called allow_mixed
(which defaults to False
):
def _cmp(self, other, allow_mixed=False)
When set to True
, which is the case when comparing using the ==
operator, it is possible to compare tz-aware and naive datetime
instances. Otherwise, it raises a TypeError:
# When testing for equality, set allow_mixed to True.
# For all the other operators, it remains False.
def __eq__(self, other):
if isinstance(other, datetime):
return self._cmp(other, allow_mixed=True) == 0
if myoff is None or otoff is None:
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware datetimes")
So, it really seems intended behaviour. In fact, Pandas' implementation of the comparisons of pandas.Timestamps
and similar is consistent with this.
My question is, what is the reasoning? I suppose, like the name of the parameter says, this way we can filter collections of datetime
objects that contain both naive and tz-aware instances (i.e., "mixed"). But wouldn't this just introduce a source of potential bugs and unintended behaviour? What am I missing?
EDIT after deceze's comments: this is in fact still "semantically correct" (i.e., the dates are for sure different).
As can be seen in the docs, up until Python 3.2 a TypeError
was actually raised in these cases:
Changed in version 3.3: Equality comparisons between aware and naive datetime instances don’t raise TypeError.
In 2012 the Python developers considered the trade-off between these two issues:
TypeError
would make it easier to catch bugs caused by the grave mistake of mixing naive and aware datetime
objects.TypeError
for datetime
objects only would break that consistency.Here's the pertinent discussion on the Python developer mailing list:
This is nice when your datetime objects are freshly created. It is not so nice when some of them already exist e.g. in a database (using an ORM layer). Mixing naive and aware datetimes is currently a catastrophe, since even basic operations such as equality comparison fail with a TypeError (it must be pretty much the only type in the stdlib with such poisonous behaviour).
Comparing aware and naive datetime objects doesn't make much sense but it's an easy mistake to make. I would say the TypeError is a sensible way to warn you while simply returning False could lead to much confusion.
You could say the same about equally "confusing" results, yet equality never raises TypeError (except between datetime instances):
>>> () == [] False
Raising an exception has very serious implications, such as making it impossible to put these objects in the same dictionary.
And even closer to home,
>>> date(2012,6,1) == datetime(2012,6,1) `False`
I agree, equality comparison should not raise an exception.
Let's make it so.
-- --Guido van Rossum (python.org/~guido)
Looks like the arguments for removing the exception were stronger in this exchange. Guido van Rossum is the creator of the Python language and had the last word on issues like this. That's why he used to be called benevolent dicator for life. So after his "Let's make it so", the behaviour was changed so that naive and aware datetime
object always compare unequal instead of raising a TypeError
.