python/cpython · error · ValueError

fromutc: dt.tzinfo is not self

Error message

fromutc: dt.tzinfo is not self

What it means

timezone.fromutc(dt) converts a UTC-wall-clock datetime into local time by adding the fixed offset — but only when dt.tzinfo is exactly this timezone instance. Passing a datetime attached to a different tzinfo object (even an equal-offset one) raises ValueError, because fromutc would otherwise produce a datetime labeled with the wrong zone.

Source

Thrown at Lib/_pydatetime.py:2537

    def tzname(self, dt):
        if isinstance(dt, datetime) or dt is None:
            if self._name is None:
                return self._name_from_offset(self._offset)
            return self._name
        raise TypeError("tzname() argument must be a datetime instance"
                        " or None")

    def dst(self, dt):
        if isinstance(dt, datetime) or dt is None:
            return None
        raise TypeError("dst() argument must be a datetime instance"
                        " or None")

    def fromutc(self, dt):
        if isinstance(dt, datetime):
            if dt.tzinfo is not self:
                raise ValueError("fromutc: dt.tzinfo "
                                 "is not self")
            return dt + self._offset
        raise TypeError("fromutc() argument must be a datetime instance"
                        " or None")

    _maxoffset = timedelta(hours=24, microseconds=-1)
    _minoffset = -_maxoffset

    @staticmethod
    def _name_from_offset(delta):
        if not delta:
            return 'UTC'
        if delta < timedelta(0):
            sign = '-'
            delta = -delta
        else:
            sign = '+'
        hours, rest = divmod(delta, timedelta(hours=1))

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Attach the zone first: tz.fromutc(dt.replace(tzinfo=tz))
  2. Prefer the public API: dt.astimezone(tz) handles attachment and fromutc correctly
  3. Ensure helper functions rebind tzinfo rather than assuming it is already set

Example fix

// before
local = my_tz.fromutc(utc_dt)  # utc_dt.tzinfo is timezone.utc

# after
local = utc_dt.astimezone(my_tz)
Defensive patterns

Strategy: validation

Validate before calling

from datetime import datetime

def from_utc(tz, dt: datetime) -> datetime:
    if dt.tzinfo is not tz:
        dt = dt.replace(tzinfo=tz)
    return tz.fromutc(dt)

Type guard

def is_bound_to(dt, tz) -> bool:
    return dt.tzinfo is tz

Try / catch

try:
    local = tz.fromutc(dt)
except ValueError:
    local = dt.astimezone(tz)  # public API handles binding

Prevention

When it happens

Trigger: utc_tz.fromutc(dt) where dt.tzinfo is timezone.utc or another timezone instance; datetime.astimezone internally relies on fromutc with self-attached datetimes, so direct misuse is the usual trigger; reusing datetimes across zone objects.

Common situations: Manual UTC→local conversions that forget dt.replace(tzinfo=tz); caching converted datetimes and re-running fromutc on them; helper functions receiving datetimes already bound to another zone.

Related errors


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