{"record":{"id":"3e81bd95faec0edf","repo":"python/cpython","slug":"cannot-compare-naive-and-aware-times","errorCode":null,"errorMessage":"cannot compare naive and aware times","messagePattern":"cannot compare naive and aware times","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1562,"sourceCode":"        myoff = otoff = None\n\n        if mytz is ottz:\n            base_compare = True\n        else:\n            myoff = self.utcoffset()\n            otoff = other.utcoffset()\n            base_compare = myoff == otoff\n\n        if base_compare:\n            return _cmp((self._hour, self._minute, self._second,\n                         self._microsecond),\n                        (other._hour, other._minute, other._second,\n                         other._microsecond))\n        if myoff is None or otoff is None:\n            if allow_mixed:\n                return 2 # arbitrary non-zero value\n            else:\n                raise TypeError(\"cannot compare naive and aware times\")\n        myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)\n        othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)\n        return _cmp((myhhmm, self._second, self._microsecond),\n                    (othhmm, other._second, other._microsecond))\n\n    def __hash__(self):\n        \"\"\"Hash.\"\"\"\n        if self._hashcode == -1:\n            if self.fold:\n                t = self.replace(fold=0)\n            else:\n                t = self\n            tzoff = t.utcoffset()\n            if not tzoff:  # zero or None\n                self._hashcode = hash(t._getstate()[0])\n            else:\n                h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,\n                              timedelta(hours=1))","sourceCodeStart":1544,"sourceCodeEnd":1580,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1544-L1580","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nif start < end_from_api:  # end_from_api is aware\n    ...\n\n# after\nstart = start.replace(tzinfo=None)\nend_from_api = end_from_api.replace(tzinfo=None)\nif start < end_from_api:\n    ...","handlingStrategy":"type-guard","validationCode":"def aware(t):\n    return t.tzinfo is not None and t.utcoffset() is not None\n\nif aware(a) != aware(b):\n    a = a.replace(tzinfo=None); b = b.replace(tzinfo=None)  # or attach UTC to both\nif a < b: ...","typeGuard":"from datetime import time as _time\n\ndef is_aware_time(t: _time) -> bool:\n    return t.tzinfo is not None and t.utcoffset() is not None","tryCatchPattern":"try:\n    return a < b\nexcept TypeError:\n    # mixed naive/aware: normalize then retry once\n    return a.replace(tzinfo=None) < b.replace(tzinfo=None)","preventionTips":["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"],"tags":["datetime","time","typeerror","naive-vs-aware","comparison"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}