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
- Make the custom utcoffset() always return a timedelta (never None) for any datetime it receives from fromutc
- Subclass fromutc() in the custom tzinfo to do the conversion directly without relying on utcoffset/dst
- 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
- Custom utcoffset() must return a timedelta for every datetime, never None
- Return None only for a None argument (the naive-datetime sentinel), as stdlib zones do
- Fuzz custom tzinfo methods over a wide date range in tests to catch None branches
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
- dt.tzinfo is not self
- fromutc() requires a non-None dst() result
- fromutc(): dt.dst gave inconsistent results; cannot convert
- 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/f825623c85b64ad5.
Report an issue: GitHub.