python/cpython · error · TypeError

offset must be a timedelta

Error message

offset must be a timedelta

What it means

timezone.__new__ requires its first argument to be a timedelta instance; it represents a fixed UTC offset as an exact duration. Passing an int (minutes/seconds ambiguity), a string, or None is rejected immediately with TypeError.

Source

Thrown at Lib/_pydatetime.py:2454

    THURSDAY = 3
    firstday = _ymd2ord(year, 1, 1)
    firstweekday = (firstday + 6) % 7  # See weekday() above
    week1monday = firstday - firstweekday
    if firstweekday > THURSDAY:
        week1monday += 7
    return week1monday


class timezone(tzinfo):
    """Fixed offset from UTC implementation of tzinfo."""

    __slots__ = '_offset', '_name'

    # Sentinel value to disallow None
    _Omitted = object()
    def __new__(cls, offset, name=_Omitted):
        if not isinstance(offset, timedelta):
            raise TypeError("offset must be a timedelta")
        if name is cls._Omitted:
            if not offset:
                return cls.utc
            name = None
        elif not isinstance(name, str):
            raise TypeError("name must be a string")
        if not cls._minoffset <= offset <= cls._maxoffset:
            raise ValueError("offset must be a timedelta "
                             "strictly between -timedelta(hours=24) and "
                             f"timedelta(hours=24), not {offset!r}")
        return cls._create(offset, name)

    def __init_subclass__(cls):
        raise TypeError("type 'datetime.timezone' is not an acceptable base type")

    @classmethod
    def _create(cls, offset, name=None):
        self = tzinfo.__new__(cls)

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Wrap the numeric seconds: timezone(timedelta(seconds=7200))
  2. For minute-based configs: timezone(timedelta(minutes=offset_minutes))
  3. For named IANA zones use ZoneInfo('Region/City') instead of timezone()
  4. Type-check config values at load time and convert to timedelta there

Example fix

// before
tz = timezone(-5 * 3600)  # intended UTC-5

# after
tz = timezone(timedelta(hours=-5))
Defensive patterns

Strategy: type-guard

Validate before calling

from datetime import timedelta, timezone

def make_timezone(offset) -> timezone:
    if isinstance(offset, int):        # seconds, dateutil-style
        offset = timedelta(seconds=offset)
    if not isinstance(offset, timedelta):
        raise TypeError('offset must be int seconds or timedelta')
    return timezone(offset)

Type guard

from datetime import timedelta

def is_timedelta(v) -> bool:
    return isinstance(v, timedelta)

Prevention

When it happens

Trigger: timezone(7200) intending seconds; timezone('-05:00'); timezone(None); timezone(5, 'EST') copying a textbook example in a language where ints worked.

Common situations: Porting code from dateutil.tz.tzoffset('name', seconds) which does accept ints; reading offset configs as numbers of seconds/minutes; timezone-aware ORM fields feeding ints into timezone().

Related errors


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