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

  1. 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)
  2. Normalize at the boundary: convert every incoming time to aware UTC (or all to naive) in one adapter layer
  3. 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

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


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