python/cpython · error · ValueError

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

Error message

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

What it means

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.

Source

Thrown at Lib/_pydatetime.py:1341

    def dst(self, dt):
        """datetime -> DST offset as timedelta, positive for east of UTC.

        Return 0 if DST not in effect.  utcoffset() must include the DST
        offset.
        """
        raise NotImplementedError("tzinfo subclass must override dst()")

    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.

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Make the custom utcoffset() always return a timedelta (never None) for any datetime it receives from fromutc
  2. Subclass fromutc() in the custom tzinfo to do the conversion directly without relying on utcoffset/dst
  3. Switch to zoneinfo.ZoneInfo, whose utcoffset is always concrete

Example fix

// before
class WeirdTZ(tzinfo):
    def utcoffset(self, dt): return None
    def dst(self, dt): return timedelta(0)
    def tzname(self, dt): return 'W'

// after
class WeirdTZ(tzinfo):
    def utcoffset(self, dt): return timedelta(hours=3)
    def dst(self, dt): return timedelta(0)
    def tzname(self, dt): return 'W'
Defensive patterns

Strategy: validation

Validate before calling

probe = dt.replace(tzinfo=tz)
if probe.utcoffset() is None:
    raise ValueError(f'{type(tz).__name__}.utcoffset returns None; cannot convert')
tz.fromutc(probe)

Type guard

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

Try / catch

try:
    local = tz.fromutc(dt)
except ValueError:
    local = dt + FIXED_FALLBACK_OFFSET  # only if a documented fixed offset exists

Prevention

When it happens

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

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

Related errors


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