{"record":{"id":"5843d78e19fedcfe","repo":"python/cpython","slug":"tzinfo-subclass-must-override-dst","errorCode":null,"errorMessage":"tzinfo subclass must override dst()","messagePattern":"tzinfo subclass must override dst\\(\\)","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1329,"sourceCode":"    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()\n        if dtoff is None:\n            raise ValueError(\"fromutc() requires a non-None utcoffset() \"\n                             \"result\")\n\n        # See the long comment block at the end of this file for an\n        # explanation of this algorithm.\n        dtdst = dt.dst()\n        if dtdst is None:","sourceCodeStart":1311,"sourceCodeEnd":1347,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1311-L1347","documentation":"Raised by the abstract base tzinfo.dst() when a timezone subclass does not override it. dst(dt) must return the daylight-saving offset as a timedelta (or zero/None when DST is not in effect); utcoffset() is expected to already include it. Any code path that asks a datetime for its DST state (e.g. the default fromutc algorithm) hits this stub on incomplete subclasses.","triggerScenarios":"class MyTZ(tzinfo): pass (or missing only dst) then datetime.now(MyTZ()).dst(); calling .astimezone() on a datetime attached to such a tzinfo, which invokes the default fromutc and its dst() call; direct MyTZ().dst(dt).","commonSituations":"Fixed-offset custom timezones where the author assumed dst() would never be called — but fromutc() and some formatting paths still invoke it; test doubles for tzinfo that implement only utcoffset.","solutions":["Override dst(self, dt) to return timedelta(0) for zones without DST","Use datetime.timezone (fixed offset, DST-free by definition) or zoneinfo.ZoneInfo (full DST rules) instead of subclassing","If overriding fromutc() yourself, ensure dst() is still defined for other callers"],"exampleFix":"// before\nclass MyTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=9)\n    def tzname(self, dt): return 'JST'\n\n// after\nclass MyTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=9)\n    def tzname(self, dt): return 'JST'\n    def dst(self, dt): return timedelta(0)","handlingStrategy":"validation","validationCode":"assert type(tz).dst is not tzinfo.dst, 'dst() not overridden'\n# also exercise the fromutc path used by astimezone\n_ = datetime(2024, 1, 1, tzinfo=tz).astimezone(timezone.utc)","typeGuard":"def tz_has_dst(tz) -> bool:\n    return type(tz).dst is not tzinfo.dst","tryCatchPattern":"try:\n    _ = dt.dst()\nexcept NotImplementedError:\n    pass  # no DST info; skip DST-dependent logic","preventionTips":["Implement dst() returning timedelta(0) for DST-free custom zones","Remember astimezone()/fromutc() call dst() even for fixed-offset zones","Cover the astimezone round-trip in tests of any custom tzinfo"],"tags":["datetime","timezone","notimplementederror","dst"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}