RustPython/RustPython · error · ValueError

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

Error message

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

What it means

The fromutc() algorithm needs dt.utcoffset() to compute the gap between UTC and the zone's standard time. If the attached tzinfo answers None — the convention for 'offset unknown', and what broken or half-implemented zones return — the conversion cannot proceed and raises this ValueError.

Source

Thrown at Lib/_pydatetime.py:1325

    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 aaeab4f754)

Solutions

  1. Use astimezone() with concrete zones (datetime.timezone, zoneinfo) that always report an offset.
  2. Make the custom utcoffset() return a timedelta for every datetime fromutc() will see; reserve None strictly for 'unknown'.
  3. Check dt.utcoffset() is not None before calling tz.fromutc(dt).

Example fix

// before
result = my_tz.fromutc(dt)  # my_tz.utcoffset(dt) returned None

// after
if dt.utcoffset() is None:
    raise ValueError(f'{my_tz!r} reports no offset for {dt}')
result = dt.astimezone(my_tz)
Defensive patterns

Strategy: validation

Validate before calling

def fromutc_safe(tz, dt):
    if dt.tzinfo is not tz:
        dt = dt.replace(tzinfo=tz)
    if dt.utcoffset() is None:
        raise ValueError(f'{tz!r} reports no utcoffset for {dt}')
    return tz.fromutc(dt)

Try / catch

try:
    local = tz.fromutc(dt)
except ValueError:
    local = dt.replace(tzinfo=timezone.utc).astimezone(tz)  # concrete-zone fallback

Prevention

When it happens

Trigger: tz.fromutc(dt) where dt.tzinfo.utcoffset(dt) returns None; a custom tzinfo whose utcoffset() has a None code path for some datetimes; zones that refuse offsets for pre-transition or ambiguous times.

Common situations: Custom tzinfo subclasses modeled on doc examples where utcoffset returns None by default; pipelines attaching half-implemented zones; porting code that assumed an offset always exists for aware datetimes.

Related errors


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/03597db1c23a126c. Report an issue: GitHub.