{"record":{"id":"6199115b59cc971a","repo":"python/cpython","slug":"tzinfo-subclass-must-override-utcoffset","errorCode":null,"errorMessage":"tzinfo subclass must override utcoffset()","messagePattern":"tzinfo subclass must override utcoffset\\(\\)","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1321,"sourceCode":"date.min = date(1, 1, 1)\ndate.max = date(9999, 12, 31)\ndate.resolution = timedelta(days=1)\n\n\nclass tzinfo:\n    \"\"\"Abstract base class for time zone info objects.\n\n    Subclasses must override the tzname(), utcoffset() and dst() methods.\n    \"\"\"\n    __slots__ = ()\n\n    def tzname(self, dt):\n        \"datetime -> string name of time zone.\"\n        raise NotImplementedError(\"tzinfo subclass must override tzname()\")\n\n    def utcoffset(self, dt):\n        \"datetime -> timedelta, positive for east of UTC, negative for west of UTC\"\n        raise NotImplementedError(\"tzinfo subclass must override utcoffset()\")\n\n    def dst(self, dt):\n        \"\"\"datetime -> DST offset as timedelta, positive for east of UTC.\n\n        Return 0 if DST not in effect.  utcoffset() must include the DST\n        offset.\n        \"\"\"\n        raise NotImplementedError(\"tzinfo subclass must override dst()\")\n\n    def fromutc(self, dt):\n        \"datetime in UTC -> datetime in local time.\"\n\n        if not isinstance(dt, datetime):\n            raise TypeError(\"fromutc() requires a datetime argument\")\n        if dt.tzinfo is not self:\n            raise ValueError(\"dt.tzinfo is not self\")\n\n        dtoff = dt.utcoffset()","sourceCodeStart":1303,"sourceCodeEnd":1339,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1303-L1339","documentation":"Raised by the abstract base tzinfo.utcoffset() when a timezone subclass does not override it. utcoffset(dt) is the core method of the tzinfo contract: it must return a timedelta (positive east of UTC) or None for a naive datetime. Without it, aware datetime arithmetic, comparison, and UTC conversion cannot function, so the base class raises NotImplementedError.","triggerScenarios":"class MyTZ(tzinfo): pass used as datetime.now(MyTZ()).utcoffset(); calling .astimezone(timezone.utc) on a datetime whose tzinfo lacks utcoffset; strftime('%z') on such a datetime; direct MyTZ().utcoffset(dt) calls.","commonSituations":"Custom tzinfo subclasses for historical or fictional timezones where only tzname was implemented; stub timezone classes created for tests; incomplete port of a timezone implementation from another language.","solutions":["Override utcoffset(self, dt) in the subclass returning a timedelta or None","Replace the custom class with zoneinfo.ZoneInfo(region/city), which implements the full tzinfo contract from the IANA database","For fixed offsets use datetime.timezone(timedelta(hours=N)) instead of subclassing"],"exampleFix":"// before\nclass MyTZ(tzinfo):\n    def tzname(self, dt): return 'X'\n\n// after\nfrom datetime import timezone, timedelta\nMyTZ = timezone(timedelta(hours=5), name='X')","handlingStrategy":"validation","validationCode":"assert tz.utcoffset(datetime.now()) is not NotImplemented\n# stronger: verify the method is not the abstract stub\nassert type(tz).utcoffset is not tzinfo.utcoffset","typeGuard":"def tz_has_utcoffset(tz) -> bool:\n    return type(tz).utcoffset is not tzinfo.utcoffset","tryCatchPattern":"try:\n    off = dt.utcoffset()\nexcept NotImplementedError:\n    off = None  # treat as naive or reject the tzinfo explicitly","preventionTips":["Use zoneinfo.ZoneInfo for real zones; datetime.timezone for fixed offsets","Unit-test every custom tzinfo against datetime.now(tz), .utcoffset(), .tzname(), .dst()","Never ship a tzinfo subclass with inherited abstract methods"],"tags":["datetime","timezone","notimplementederror","utc-offset"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}