python/cpython · error · TypeError

tzinfo.tzname() must return None or string, not {type(name).

Error message

tzinfo.tzname() must return None or string, not {type(name).__name__!r}

What it means

Raised by datetime's internal _check_tzname() when a custom tzinfo subclass's tzname(dt) method returns a value that is neither None nor a str. The datetime module calls tzname() internally (e.g. when formatting aware datetimes or computing tzname()) and enforces the return-type contract of the tzinfo ABC. Any non-string truthy value (int, bytes, tuple) triggers this TypeError.

Source

Thrown at Lib/_pydatetime.py:547

            raise ValueError(f"Invalid week: {week}")

    if not 0 < day < 8:
        raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")

    # Now compute the offset from (Y, 1, 1) in days:
    day_offset = (week - 1) * 7 + (day - 1)

    # Calculate the ordinal day for monday, week 1
    day_1 = _isoweek1monday(year)
    ord_day = day_1 + day_offset

    return _ord2ymd(ord_day)


# Just raise TypeError if the arg isn't None or a string.
def _check_tzname(name):
    if name is not None and not isinstance(name, str):
        raise TypeError("tzinfo.tzname() must return None or string, "
                        f"not {type(name).__name__!r}")

# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
# If offset isn't None or timedelta, raises TypeError.
# If offset is None, returns None.
# Else offset is checked for being in range.
# If it is, its integer value is returned.  Else ValueError is raised.
def _check_utc_offset(name, offset):
    assert name in ("utcoffset", "dst")
    if offset is None:
        return
    if not isinstance(offset, timedelta):
        raise TypeError(f"tzinfo.{name}() must return None "
                        f"or timedelta, not {type(offset).__name__!r}")
    if not -timedelta(1) < offset < timedelta(1):
        raise ValueError("offset must be a timedelta "
                         "strictly between -timedelta(hours=24) and "

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Make the custom tzinfo.tzname(dt) return a str (e.g. 'EST') or None
  2. If a non-string identifier is needed internally, keep it in a separate attribute/method and only return its str() form from tzname()
  3. Check every tzinfo hook (utcoffset, dst, tzname) against the ABC contract in the docs

Example fix

// before
class MyTZ(tzinfo):
    def tzname(self, dt):
        return -5  # int -> TypeError
// after
class MyTZ(tzinfo):
    def tzname(self, dt):
        return 'UTC-05:00'
Defensive patterns

Strategy: type-guard

Validate before calling

name = tz.tzname(dt)
assert name is None or isinstance(name, str), 'tzname() must return None or str'

Type guard

def valid_tzname(v) -> bool:
    return v is None or isinstance(v, str)

Prevention

When it happens

Trigger: Calling dt.strftime('%Z') or datetime.tzname() on an aware datetime whose tzinfo subclass returns e.g. an int offset or bytes from tzname(); also astimezone()/isoformat() paths that consult tzname().

Common situations: Custom timezone wrappers that return an offset code (e.g. -5) instead of 'UTC-05:00'; returning bytes from a tzname implementation ported from Python 2; stub tzinfo classes in tests that return mock objects.

Related errors


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