python/cpython · error · TypeError
cannot compare naive and aware times
Error message
cannot compare naive and aware times
What it means
Raised by time.__cmp-level comparison when one time is naive (tzinfo None) and the other is aware (has an offset), and allow_mixed is false — i.e. ordinary ordering comparisons (==/!= never raise; <, >, <=, >= and sorting do). Comparing wall-clock times across different offsets requires both offsets to be known; a missing offset makes ordering undefined, so Python raises TypeError rather than guessing.
Source
Thrown at Lib/_pydatetime.py:1562
myoff = otoff = None
if mytz is ottz:
base_compare = True
else:
myoff = self.utcoffset()
otoff = other.utcoffset()
base_compare = myoff == otoff
if base_compare:
return _cmp((self._hour, self._minute, self._second,
self._microsecond),
(other._hour, other._minute, other._second,
other._microsecond))
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 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),
(othhmm, other._second, other._microsecond))
def __hash__(self):
"""Hash."""
if self._hashcode == -1:
if self.fold:
t = self.replace(fold=0)
else:
t = self
tzoff = t.utcoffset()
if not tzoff: # zero or None
self._hashcode = hash(t._getstate()[0])
else:
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
timedelta(hours=1))View on GitHub (pinned to bc6749cc3b)
Solutions
- Make both operands the same kind: attach an offset with t.replace(tzinfo=timezone.utc) or strip it from the aware one via t.replace(tzinfo=None)
- Normalize at the boundary: convert every incoming time to aware UTC (or all to naive) in one adapter layer
- For display-only comparisons of wall-clock times, compare (t.hour, t.minute, t.second, t.microsecond) tuples explicitly
Example fix
# before
if start < end_from_api: # end_from_api is aware
...
# after
start = start.replace(tzinfo=None)
end_from_api = end_from_api.replace(tzinfo=None)
if start < end_from_api:
... Defensive patterns
Strategy: type-guard
Validate before calling
def aware(t):
return t.tzinfo is not None and t.utcoffset() is not None
if aware(a) != aware(b):
a = a.replace(tzinfo=None); b = b.replace(tzinfo=None) # or attach UTC to both
if a < b: ... Type guard
from datetime import time as _time
def is_aware_time(t: _time) -> bool:
return t.tzinfo is not None and t.utcoffset() is not None Try / catch
try:
return a < b
except TypeError:
# mixed naive/aware: normalize then retry once
return a.replace(tzinfo=None) < b.replace(tzinfo=None) Prevention
- Normalize every time to one convention (all naive or all aware) at the system boundary
- Remember ==/!= are safe but ordering raises — audit sorts and min/max over time collections
- Attach timezone.utc when parsing ISO strings that carry an offset
When it happens
Trigger: time(12, 0) < time(12, 0, tzinfo=timezone.utc); sorting a list mixing naive and aware times; time(9,30) <= db_time where the DB driver returns aware times; max()/min() over heterogeneous time collections.
Common situations: Mixing times parsed from ISO strings with trailing 'Z'/'+00:00' (aware, via fromisoformat) with locally constructed naive times; DBALs such as psycopg returning aware times; refactors that attach timezone.utc to some code paths but not others; equality tests pass, then a later sort fails.
Related errors
- fromisoformat: argument must be str
- bad tzinfo state arg
- tzinfo.tzname() must return None or string, not {type(name).
- tzinfo.{name}() must return None or timedelta, not {type(off
- hour must be in 0..23, not {hour}
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/3e81bd95faec0edf.
Report an issue: GitHub.