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

  1. Attach a timezone to the naive side: naive.replace(tzinfo=timezone.utc) (only if it truly is UTC)
  2. Make both naive consistently: dt_aware.replace(tzinfo=None) or dt_aware.astimezone().replace(tzinfo=None) for local
  3. Standardize the codebase on aware datetimes (UTC) end-to-end
  4. 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

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


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/3c9d53fbfcbb44d5. Report an issue: GitHub.