{"record":{"id":"dadd8357cb94850e","repo":"python/cpython","slug":"fromutc-dt-dst-gave-inconsistent-results-canno","errorCode":null,"errorMessage":"fromutc(): dt.dst gave inconsistent results; cannot convert","messagePattern":"fromutc\\(\\): dt\\.dst gave inconsistent results; cannot convert","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":1354,"sourceCode":"        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__())\n\n\nclass IsoCalendarDate(tuple):\n\n    def __new__(cls, year, week, weekday, /):\n        return super().__new__(cls, (year, week, weekday))","sourceCodeStart":1336,"sourceCodeEnd":1372,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L1336-L1372","documentation":"Raised by tzinfo.fromutc() when dst() returns a value before the adjustment but None after the datetime was shifted by delta = utcoffset - dst. The default algorithm assumes dst() is consistent across that small time shift; getting a timedelta first and then None means the timezone implementation contradicts itself, so conversion is abandoned with this ValueError.","triggerScenarios":"A custom dst(self, dt) that returns timedelta(hours=1) for some clock times and None for others, where the shifted time lands in the None branch; dst() keyed on wall-clock hour with gaps; zone implementations whose DST predicate changes discontinuously within the offset window.","commonSituations":"Hand-written DST rules that use wall-clock comparisons without normalization; timezone mocks in tests that vary by call count or hour; buggy ports of IANA transition tables.","solutions":["Make dst() deterministic and total: return a timedelta (possibly timedelta(0)) for every datetime, never None inside fromutc's operating range","Override fromutc() in the subclass with transition-aware logic instead of relying on the base algorithm","Adopt zoneinfo.ZoneInfo, which has correct, consistent transition handling"],"exampleFix":"// before\nclass BadTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=1)\n    def dst(self, dt):\n        return None if (dt and dt.hour < 3) else timedelta(hours=1)\n    def tzname(self, dt): return 'B'\n\n// after\nclass BadTZ(tzinfo):\n    def utcoffset(self, dt): return timedelta(hours=1)\n    def dst(self, dt): return timedelta(hours=1)\n    def tzname(self, dt): return 'B'","handlingStrategy":"validation","validationCode":"# verify dst() is stable under the offset shift fromutc performs\nd1 = dt.replace(tzinfo=tz).dst()\nif d1 is not None:\n    d2 = (dt - (dt.replace(tzinfo=tz).utcoffset() - d1)).replace(tzinfo=tz).dst()\n    if d2 is None:\n        raise ValueError('dst() inconsistent under offset shift')","typeGuard":null,"tryCatchPattern":"try:\n    local = tz.fromutc(dt)\nexcept ValueError as e:\n    if 'inconsistent' in str(e):\n        raise RuntimeError(f'broken tzinfo implementation: {type(tz).__name__}') from e\n    raise","preventionTips":["Write dst() as a pure function of the datetime — no call-count or wall-clock gaps","Property-test custom zones: dst() must never return None once it returned a timedelta nearby","Use zoneinfo.ZoneInfo for real-world transition rules"],"tags":["datetime","timezone","valueerror","fromutc","dst","consistency"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}