{"record":{"id":"f0b3e01544be0d64","repo":"python/cpython","slug":"tzinfo-name-must-return-none-or-timedelta-not","errorCode":null,"errorMessage":"tzinfo.{name}() must return None or timedelta, not {type(offset).__name__!r}","messagePattern":"tzinfo\\.(.+?)\\(\\) must return None or timedelta, not (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":561,"sourceCode":"\n# Just raise TypeError if the arg isn't None or a string.\ndef _check_tzname(name):\n    if name is not None and not isinstance(name, str):\n        raise TypeError(\"tzinfo.tzname() must return None or string, \"\n                        f\"not {type(name).__name__!r}\")\n\n# name is the offset-producing method, \"utcoffset\" or \"dst\".\n# offset is what it returned.\n# If offset isn't None or timedelta, raises TypeError.\n# If offset is None, returns None.\n# Else offset is checked for being in range.\n# If it is, its integer value is returned.  Else ValueError is raised.\ndef _check_utc_offset(name, offset):\n    assert name in (\"utcoffset\", \"dst\")\n    if offset is None:\n        return\n    if not isinstance(offset, timedelta):\n        raise TypeError(f\"tzinfo.{name}() must return None \"\n                        f\"or timedelta, not {type(offset).__name__!r}\")\n    if not -timedelta(1) < offset < timedelta(1):\n        raise ValueError(\"offset must be a timedelta \"\n                         \"strictly between -timedelta(hours=24) and \"\n                         f\"timedelta(hours=24), not {offset!r}\")\n\ndef _check_date_fields(year, month, day):\n    year = _index(year)\n    month = _index(month)\n    day = _index(day)\n    if not MINYEAR <= year <= MAXYEAR:\n        raise ValueError(f\"year must be in {MINYEAR}..{MAXYEAR}, not {year}\")\n    if not 1 <= month <= 12:\n        raise ValueError(f\"month must be in 1..12, not {month}\")\n    dim = _days_in_month(year, month)\n    if not 1 <= day <= dim:\n        raise ValueError(f\"day {day} must be in range 1..{dim} for month {month} in year {year}\")\n    return year, month, day","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L543-L579","documentation":"Raised by _check_utc_offset() when a tzinfo subclass's utcoffset(dt) or dst(dt) method returns something that is neither None nor a datetime.timedelta. The message names which method misbehaved. The datetime module validates these return values whenever it needs an offset (aware arithmetic, comparisons, strftime, timestamp()).","triggerScenarios":"A custom tzinfo whose utcoffset() returns an int like 3600 (seconds) instead of timedelta(hours=1); a dst() that returns 0 or a string; called via dt.astimezone(), dt.utcoffset(), aware comparisons, or isoformat() on an aware datetime.","commonSituations":"Developers assuming seconds-based offsets from other languages/libraries; tzinfo shims around pytz or dateutil returning raw numbers; test doubles returning MagicMock instead of timedelta.","solutions":["Return datetime.timedelta from utcoffset() and dst() (e.g. timedelta(hours=5)) or None for unknown","Wrap numeric offsets: timedelta(seconds=offset) instead of a bare int","In tests, configure mocks with return_value=timedelta(0), not integers"],"exampleFix":"// before\nclass MyTZ(tzinfo):\n    def utcoffset(self, dt):\n        return 3600  # seconds -> TypeError\n// after\nclass MyTZ(tzinfo):\n    def utcoffset(self, dt):\n        return timedelta(hours=1)","handlingStrategy":"type-guard","validationCode":"off = tz.utcoffset(dt)\nassert off is None or isinstance(off, timedelta), 'utcoffset() must return None or timedelta'","typeGuard":"from datetime import timedelta\n\ndef valid_offset(v) -> bool:\n    return v is None or isinstance(v, timedelta)","tryCatchPattern":null,"preventionTips":["Wrap numeric offsets in timedelta at the source, never return bare ints from utcoffset/dst","Configure test mocks with return_value=timedelta(hours=N)"],"tags":["datetime","tzinfo","typeerror","timezone"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}