{"record":{"id":"64a27c82ed2f17b9","repo":"python/cpython","slug":"fromutc-requires-a-non-none-dst-result","errorCode":null,"errorMessage":"fromutc() requires a non-None dst() result","messagePattern":"fromutc\\(\\) requires a non-None dst\\(\\) result","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1348,"sourceCode":"\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:\n            raise ValueError(\"fromutc() requires a non-None dst() result\")\n        delta = dtoff - dtdst\n        if delta:\n            dt += delta\n            dtdst = dt.dst()\n            if dtdst is None:\n                raise ValueError(\"fromutc(): dt.dst gave inconsistent \"\n                                 \"results; cannot convert\")\n        return dt + dtdst\n\n    # Pickle support.\n\n    def __reduce__(self):\n        getinitargs = getattr(self, \"__getinitargs__\", None)\n        if getinitargs:\n            args = getinitargs()\n        else:\n            args = ()\n        return (self.__class__, args, self.__getstate__())","sourceCodeStart":1330,"sourceCodeEnd":1366,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1330-L1366","documentation":"Raised by tzinfo.fromutc() when dt.dst() returns None on the first call. The default fromutc algorithm computes delta = utcoffset - dst and applies it, so a None DST value breaks the arithmetic. A tzinfo may legitimately return None for dst() on naive datetimes (the argument is None), but fromutc always passes a real datetime, so returning None there signals an incomplete or sentinel implementation.","triggerScenarios":"Custom tzinfo whose dst(self, dt) returns None unconditionally; dst() implementations that return None for dt values they classify as ambiguous; calling .astimezone()/fromutc on datetimes attached to such a zone.","commonSituations":"Fixed-offset zones written with `def dst(self, dt): return None` copied from an old recipe (the correct no-DST value is timedelta(0)); tzinfo fakes in test suites that stub dst with None.","solutions":["Return timedelta(0) instead of None from dst() for zones without daylight saving","Override fromutc() in the custom tzinfo so it does not depend on dst()","Replace the custom zone with datetime.timezone or zoneinfo.ZoneInfo"],"exampleFix":"// before\nclass FixedTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=2)\n    def dst(self, dt): return None\n    def tzname(self, dt): return 'F'\n\n// after\nclass FixedTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=2)\n    def dst(self, dt): return timedelta(0)\n    def tzname(self, dt): return 'F'","handlingStrategy":"validation","validationCode":"probe = dt.replace(tzinfo=tz)\nif probe.dst() is None:\n    raise ValueError('dst() returned None; use timedelta(0) for no-DST zones')\ntz.fromutc(probe)","typeGuard":"def tz_dst_is_concrete(tz, dt) -> bool:\n    return dt.replace(tzinfo=tz).dst() is not None","tryCatchPattern":"try:\n    local = tz.fromutc(dt)\nexcept ValueError as e:\n    if 'dst()' in str(e):\n        local = dt + tz.utcoffset(dt)  # fallback treating offset as fixed\n    else:\n        raise","preventionTips":["Use timedelta(0), not None, to express 'no DST' in custom zones","Test the full fromutc/astimezone path, not just individual methods","Adopt zoneinfo instead of hand-rolled DST logic"],"tags":["datetime","timezone","valueerror","fromutc","dst"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}