python/cpython · error · TypeError

bad tzinfo state arg

Error message

bad tzinfo state arg

What it means

Raised by time.__setstate (pickle reconstruction) when the second state element, the tzinfo, is neither None nor a tzinfo instance. The 6-byte base state is unpacked first, then the tzinfo slot is type-checked; passing a string, a timezone name, or an arbitrary object as the tzinfo component fails with this TypeError. It guards against corrupted or hand-built pickle states.

Source

Thrown at Lib/_pydatetime.py:1758

    # Pickle support.

    def _getstate(self, protocol=3):
        us2, us3 = divmod(self._microsecond, 256)
        us1, us2 = divmod(us2, 256)
        h = self._hour
        if self._fold and protocol > 3:
            h += 128
        basestate = bytes([h, self._minute, self._second,
                           us1, us2, us3])
        if self._tzinfo is None:
            return (basestate,)
        else:
            return (basestate, self._tzinfo)

    def __setstate(self, string, tzinfo):
        if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
            raise TypeError("bad tzinfo state arg")
        h, self._minute, self._second, us1, us2, us3 = string
        if h > 127:
            self._fold = 1
            self._hour = h - 128
        else:
            self._fold = 0
            self._hour = h
        self._microsecond = (((us1 << 8) | us2) << 8) | us3
        self._tzinfo = tzinfo

    def __reduce_ex__(self, protocol):
        return (self.__class__, self._getstate(protocol))

    def __reduce__(self):
        return self.__reduce_ex__(2)

_time_class = time  # so functions w/ args named "time" can get at the class

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Keep tzinfo objects (or None) in the state; serialize the zone name separately and rebuild via zoneinfo.ZoneInfo(name) after loading
  2. Remove custom __reduce_ex__ overrides that mangle the state layout
  3. If subclassing time, delegate pickling to the parent and only add picklable attributes

Example fix

# before
class T(time):
    def __reduce__(self):
        return (time, (self._getstate()[0], 'UTC'))  # str tzinfo -> TypeError on load

# after
from zoneinfo import ZoneInfo
class T(time):
    def __reduce__(self):
        state, tz = self._getstate()
        tz = tz if tz is None else tz  # keep real tzinfo object
        return (time, (state, tz))
Defensive patterns

Strategy: validation

Validate before calling

from datetime import tzinfo as _tz
def valid_time_state(state):
    tz = state[1] if len(state) > 1 else None
    return tz is None or isinstance(tz, _tz)

Type guard

from datetime import tzinfo as _tz

def is_valid_tz_arg(v) -> bool:
    return v is None or isinstance(v, _tz)

Try / catch

try:
    obj = pickle.loads(data)
except TypeError as e:
    if 'bad tzinfo state arg' in str(e):
        raise RuntimeError('corrupt or rewritten pickle state for datetime.time') from e
    raise

Prevention

When it happens

Trigger: Constructing time via its pickle form with a bad second argument, e.g. time(b'\x0c\x00\x00\x00\x00\x00', 'UTC'); __setstate__ invoked with a state tuple whose element 1 is a str offset like '+05:00'; custom __reduce_ex__ implementations on subclasses that emit a non-tzinfo object.

Common situations: Hand-crafted or manipulated pickles; subclass overrides of __reduce__/__getstate__ that replace the tzinfo with a serializable surrogate (name string) and forget to restore the object on load; pickle round-trips through frameworks that rewrite state tuples.

Related errors


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