{"record":{"id":"f825623c85b64ad5","repo":"python/cpython","slug":"fromutc-requires-a-non-none-utcoffset-result","errorCode":null,"errorMessage":"fromutc() requires a non-None utcoffset() result","messagePattern":"fromutc\\(\\) requires a non-None utcoffset\\(\\) result","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1341,"sourceCode":"    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:\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","sourceCodeStart":1323,"sourceCodeEnd":1359,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1323-L1359","documentation":"Raised by tzinfo.fromutc() when dt.utcoffset() returns None during the conversion. fromutc needs the UTC offset to compute the local-time adjustment (delta = utcoffset - dst); a None offset means the attached tzinfo reports 'unknown offset' for that datetime, so the arithmetic is impossible. This typically happens with a custom tzinfo whose utcoffset() returns None for some or all datetimes.","triggerScenarios":"Custom tz subclass where utcoffset(self, dt) returns None (e.g. `return None if dt is None else ...` and dt is a naive-pattern value); a tzinfo that returns None to signal 'naive'; calling fromutc on a datetime whose tzinfo delegates to an unfinished implementation.","commonSituations":"Writing a tzinfo wrapper that returns None as a sentinel; partially implemented zone classes; third-party tzinfo implementations (e.g. some test fakes) that return None offsets for out-of-range years.","solutions":["Make the custom utcoffset() always return a timedelta (never None) for any datetime it receives from fromutc","Subclass fromutc() in the custom tzinfo to do the conversion directly without relying on utcoffset/dst","Switch to zoneinfo.ZoneInfo, whose utcoffset is always concrete"],"exampleFix":"// before\nclass WeirdTZ(tzinfo):\n    def utcoffset(self, dt): return None\n    def dst(self, dt): return timedelta(0)\n    def tzname(self, dt): return 'W'\n\n// after\nclass WeirdTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=3)\n    def dst(self, dt): return timedelta(0)\n    def tzname(self, dt): return 'W'","handlingStrategy":"validation","validationCode":"probe = dt.replace(tzinfo=tz)\nif probe.utcoffset() is None:\n    raise ValueError(f'{type(tz).__name__}.utcoffset returns None; cannot convert')\ntz.fromutc(probe)","typeGuard":"def tz_offset_is_concrete(tz, dt) -> bool:\n    return dt.replace(tzinfo=tz).utcoffset() is not None","tryCatchPattern":"try:\n    local = tz.fromutc(dt)\nexcept ValueError:\n    local = dt + FIXED_FALLBACK_OFFSET  # only if a documented fixed offset exists","preventionTips":["Custom utcoffset() must return a timedelta for every datetime, never None","Return None only for a None argument (the naive-datetime sentinel), as stdlib zones do","Fuzz custom tzinfo methods over a wide date range in tests to catch None branches"],"tags":["datetime","timezone","valueerror","fromutc","utc-offset"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}