python/cpython · error · ValueError

fromutc() requires a non-None dst() result

Error message

fromutc() requires a non-None dst() result

What it means

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.

Source

Thrown at Lib/_pydatetime.py:1348

    def fromutc(self, dt):
        "datetime in UTC -> datetime in local time."

        if not isinstance(dt, datetime):
            raise TypeError("fromutc() requires a datetime argument")
        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__())

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Return timedelta(0) instead of None from dst() for zones without daylight saving
  2. Override fromutc() in the custom tzinfo so it does not depend on dst()
  3. Replace the custom zone with datetime.timezone or zoneinfo.ZoneInfo

Example fix

// before
class FixedTZ(tzinfo):
    def utcoffset(self, dt): return timedelta(hours=2)
    def dst(self, dt): return None
    def tzname(self, dt): return 'F'

// after
class FixedTZ(tzinfo):
    def utcoffset(self, dt): return timedelta(hours=2)
    def dst(self, dt): return timedelta(0)
    def tzname(self, dt): return 'F'
Defensive patterns

Strategy: validation

Validate before calling

probe = dt.replace(tzinfo=tz)
if probe.dst() is None:
    raise ValueError('dst() returned None; use timedelta(0) for no-DST zones')
tz.fromutc(probe)

Type guard

def tz_dst_is_concrete(tz, dt) -> bool:
    return dt.replace(tzinfo=tz).dst() is not None

Try / catch

try:
    local = tz.fromutc(dt)
except ValueError as e:
    if 'dst()' in str(e):
        local = dt + tz.utcoffset(dt)  # fallback treating offset as fixed
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/64a27c82ed2f17b9. Report an issue: GitHub.