python/cpython · error · ValueError
fromutc(): dt.dst gave inconsistent results; cannot convert
Error message
fromutc(): dt.dst gave inconsistent results; cannot convert
What it means
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.
Source
Thrown at Lib/_pydatetime.py:1354
if dt.tzinfo is not self:
raise ValueError("dt.tzinfo is not self")
dtoff = dt.utcoffset()
if dtoff is None:
raise ValueError("fromutc() requires a non-None utcoffset() "
"result")
# See the long comment block at the end of this file for an
# explanation of this algorithm.
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc() requires a non-None dst() result")
delta = dtoff - dtdst
if delta:
dt += delta
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc(): dt.dst gave inconsistent "
"results; cannot convert")
return dt + dtdst
# Pickle support.
def __reduce__(self):
getinitargs = getattr(self, "__getinitargs__", None)
if getinitargs:
args = getinitargs()
else:
args = ()
return (self.__class__, args, self.__getstate__())
class IsoCalendarDate(tuple):
def __new__(cls, year, week, weekday, /):
return super().__new__(cls, (year, week, weekday))View on GitHub (pinned to bc6749cc3b)
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
Example fix
// before
class BadTZ(tzinfo):
def utcoffset(self, dt): return timedelta(hours=1)
def dst(self, dt):
return None if (dt and dt.hour < 3) else timedelta(hours=1)
def tzname(self, dt): return 'B'
// after
class BadTZ(tzinfo):
def utcoffset(self, dt): return timedelta(hours=1)
def dst(self, dt): return timedelta(hours=1)
def tzname(self, dt): return 'B' Defensive patterns
Strategy: validation
Validate before calling
# verify dst() is stable under the offset shift fromutc performs
d1 = dt.replace(tzinfo=tz).dst()
if d1 is not None:
d2 = (dt - (dt.replace(tzinfo=tz).utcoffset() - d1)).replace(tzinfo=tz).dst()
if d2 is None:
raise ValueError('dst() inconsistent under offset shift') Try / catch
try:
local = tz.fromutc(dt)
except ValueError as e:
if 'inconsistent' in str(e):
raise RuntimeError(f'broken tzinfo implementation: {type(tz).__name__}') from e
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- fromutc() requires a non-None dst() result
- dt.tzinfo is not self
- fromutc() requires a non-None utcoffset() result
- Malformed time zone string
- offset must be a timedelta strictly between -timedelta(hours
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/dadd8357cb94850e.
Report an issue: GitHub.