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

  1. Attach the tzinfo before converting: dt = dt.replace(tzinfo=tz); local = tz.fromutc(dt)
  2. Prefer dt.astimezone(tz) which handles attachment and fold correctly for stdlib timezones
  3. 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

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


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