python/cpython · error · TypeError

tz argument must be an instance of tzinfo

Error message

tz argument must be an instance of tzinfo

What it means

datetime.astimezone(tz) requires tz to be a tzinfo instance (or None, which selects the local timezone). Any other value — string zone name, int offset, ZoneInfo-less code path — triggers this TypeError before any conversion happens.

Source

Thrown at Lib/_pydatetime.py:2131

            ts = self._mktime()
            # Detect gap
            ts2 = self.replace(fold=1-self.fold)._mktime()
            if ts2 != ts: # This happens in a gap or a fold
                if (ts2 > ts) == self.fold:
                    ts = ts2
        else:
            ts = (self - _EPOCH) // timedelta(seconds=1)
        localtm = _time.localtime(ts)
        # Extract TZ data
        gmtoff = localtm.tm_gmtoff
        zone = localtm.tm_zone
        return timezone(timedelta(seconds=gmtoff), zone)

    def astimezone(self, tz=None):
        if tz is None:
            tz = self._local_timezone()
        elif not isinstance(tz, tzinfo):
            raise TypeError("tz argument must be an instance of tzinfo")

        mytz = self.tzinfo
        if mytz is None:
            mytz = self._local_timezone()
            myoffset = mytz.utcoffset(self)
        else:
            myoffset = mytz.utcoffset(self)
            if myoffset is None:
                mytz = self.replace(tzinfo=None)._local_timezone()
                myoffset = mytz.utcoffset(self)

        if tz is mytz:
            return self

        # Convert self to UTC, and attach the new time zone object.
        utc = (self - myoffset).replace(tzinfo=tz)

        # Convert from UTC to tz's local time.

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Convert string names: dt.astimezone(ZoneInfo('US/Eastern'))
  2. Wrap fixed offsets: dt.astimezone(timezone(timedelta(hours=2)))
  3. For UTC use dt.astimezone(timezone.utc)
  4. Check for shadowed imports of timezone/ZoneInfo in the module

Example fix

// before
dt = dt.astimezone('UTC')

# after
from zoneinfo import ZoneInfo
dt = dt.astimezone(ZoneInfo('UTC'))
Defensive patterns

Strategy: type-guard

Validate before calling

from datetime import tzinfo

def as_tzinfo(tz):
    if tz is None or isinstance(tz, tzinfo):
        return tz
    if isinstance(tz, str):
        from zoneinfo import ZoneInfo
        return ZoneInfo(tz)
    raise TypeError(f'cannot interpret {tz!r} as a timezone')

Type guard

from datetime import tzinfo

def is_tzinfo(v) -> bool:
    return v is None or isinstance(v, tzinfo)

Prevention

When it happens

Trigger: dt.astimezone('UTC'), dt.astimezone(0), or dt.astimezone(timezone.utc) where timezone was shadowed/not imported. Passing pytz timezone objects works (they are tzinfo subclasses), but raw strings never do.

Common situations: Config files storing TZ as a string ('US/Eastern') passed directly; forgetting to wrap offsets: astimezone(timedelta(hours=2)) instead of astimezone(timezone(timedelta(hours=2))); mixing up ZoneInfo('UTC') with the literal 'UTC'.

Related errors


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