python/cpython · error · ValueError
dt.tzinfo is not self
Error message
dt.tzinfo is not self
What it means
Raised by tzinfo.fromutc() when the passed datetime's tzinfo attribute is not the same object as the tzinfo instance on which fromutc was called. The default fromutc algorithm assumes dt is annotated with self so that dt.utcoffset() and dt.dst() resolve through this timezone; a datetime carrying a different (or no) tzinfo makes the conversion meaningless.
Source
Thrown at Lib/_pydatetime.py:1337
def utcoffset(self, dt):
"datetime -> timedelta, positive for east of UTC, negative for west of UTC"
raise NotImplementedError("tzinfo subclass must override utcoffset()")
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")View on GitHub (pinned to bc6749cc3b)
Solutions
- Attach the tzinfo before converting: dt = dt.replace(tzinfo=tz); local = tz.fromutc(dt)
- Prefer dt.astimezone(tz) which handles attachment and fold correctly for stdlib timezones
- Use datetime.fromtimestamp(dt.replace(tzinfo=None).timestamp(), tz) as an alternative conversion route
Example fix
// before local = eastern.fromutc(datetime(2024,6,1,12)) # tzinfo is None -> ValueError // after local = eastern.fromutc(datetime(2024,6,1,12).replace(tzinfo=eastern))
Defensive patterns
Strategy: validation
Validate before calling
if dt.tzinfo is not tz:
dt = dt.replace(tzinfo=tz)
local = tz.fromutc(dt) Type guard
def dt_attached_to(dt, tz) -> bool:
return dt.tzinfo is tz Try / catch
try:
local = tz.fromutc(dt)
except ValueError as e:
if 'tzinfo is not self' in str(e):
local = tz.fromutc(dt.replace(tzinfo=tz))
else:
raise Prevention
- Use dt.astimezone(tz) instead of calling fromutc manually
- Attach the target tzinfo with replace() before any manual fromutc call
- Keep one conversion helper so attachment rules are applied uniformly
When it happens
Trigger: tz_a.fromutc(dt) where dt.tzinfo is tz_b or None; reusing a datetime created with timezone.utc and calling my_custom_tz.fromutc(dt) without re-attaching my_custom_tz; astimezone paths on subclasses that call self.fromutc on datetimes they did not construct.
Common situations: Manual UTC-to-local conversion where the developer forgets dt.replace(tzinfo=tz); mixing pytz-localized datetimes with stdlib fromutc; generic converters that receive datetimes from many sources.
Related errors
- fromutc() requires a non-None utcoffset() result
- 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/d83d0a66455e2938.
Report an issue: GitHub.