python/cpython · error · TypeError
cannot compare naive and aware datetimes
Error message
cannot compare naive and aware datetimes
What it means
Ordering comparisons (<, >, <=, >=) between a naive datetime (no tzinfo) and an aware datetime (with tzinfo) are forbidden because there is no defined answer without knowing the naive side's zone. Equality (==) is allowed and returns False, but ordering raises TypeError.
Source
Thrown at Lib/_pydatetime.py:2323
if allow_mixed:
if myoff != self.replace(fold=not self.fold).utcoffset():
return 2
if otoff != other.replace(fold=not other.fold).utcoffset():
return 2
base_compare = myoff == otoff
if base_compare:
return _cmp((self._year, self._month, self._day,
self._hour, self._minute, self._second,
self._microsecond),
(other._year, other._month, other._day,
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 datetimes")
# XXX What follows could be done more efficiently...
diff = self - other # this will take offsets into account
if diff.days < 0:
return -1
return diff and 1 or 0
def __add__(self, other):
"Add a datetime and a timedelta."
if not isinstance(other, timedelta):
return NotImplemented
delta = timedelta(self.toordinal(),
hours=self._hour,
minutes=self._minute,
seconds=self._second,
microseconds=self._microsecond)
delta += other
hour, rem = divmod(delta.seconds, 3600)
minute, second = divmod(rem, 60)View on GitHub (pinned to bc6749cc3b)
Solutions
- Attach a timezone to the naive side: naive.replace(tzinfo=timezone.utc) (only if it truly is UTC)
- Make both naive consistently: dt_aware.replace(tzinfo=None) or dt_aware.astimezone().replace(tzinfo=None) for local
- Standardize the codebase on aware datetimes (UTC) end-to-end
- If mixing is intentional, compare timestamps as .timestamp() floats
Example fix
// before if datetime.now() < row['created_at_aware']: ... # after if datetime.now(timezone.utc) < row['created_at_aware']: ...
Defensive patterns
Strategy: validation
Validate before calling
from datetime import datetime, timezone
def ensure_aware(dt: datetime, *, assume_utc=True) -> datetime:
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc if assume_utc else None)
return dt
if ensure_aware(a) < ensure_aware(b): ... Type guard
from datetime import datetime
def is_aware(dt: datetime) -> bool:
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None Try / catch
try:
_ = a < b
except TypeError:
a, b = ensure_aware(a), ensure_aware(b)
_ = a < b Prevention
- Adopt aware-UTC everywhere (e.g. USE_TZ=True)
- Assert awareness in test fixtures
- Check dt.tzinfo is not None before comparisons at boundaries
When it happens
Trigger: Comparing datetime.now() (naive) with datetime.now(timezone.utc) (aware); sorting a list mixing DB naive timestamps with API aware timestamps; min()/max() over mixed collections.
Common situations: Legacy databases storing naive UTC datetimes while modern APIs emit ISO strings with offsets; mixing Django USE_TZ=True objects with manual naive ones; tests asserting now() < fetched_ts across systems.
Related errors
- tz argument must be an instance of tzinfo
- cannot mix naive and timezone-aware time
- offset must be a timedelta
- name must be a string
- type 'datetime.timezone' is not an acceptable base type
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/3c9d53fbfcbb44d5.
Report an issue: GitHub.