{"record":{"id":"10609ad52498a4f1","repo":"python/cpython","slug":"offset-must-be-a-timedelta-strictly-between-timed","errorCode":null,"errorMessage":"offset must be a timedelta strictly between -timedelta(hours=24) and timedelta(hours=24), not {offset!r}","messagePattern":"offset must be a timedelta strictly between -timedelta\\(hours=24\\) and timedelta\\(hours=24\\), not (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":564,"sourceCode":"    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\n\ndef _check_time_fields(hour, minute, second, microsecond, fold):\n    hour = _index(hour)","sourceCodeStart":546,"sourceCodeEnd":582,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L546-L582","documentation":"Raised by _check_utc_offset() when utcoffset() or dst() returns a timedelta whose absolute value is not strictly less than one day (timedelta(1)). UTC offsets physically cannot reach ±24 hours, so datetime enforces -timedelta(hours=24) < offset < timedelta(hours=24). Note the strictness: exactly ±24h is also rejected.","triggerScenarios":"A custom tzinfo returning timedelta(hours=24) or timedelta(days=2) from utcoffset(); a bug computing offset as total days instead of hours; offsets accumulated across DST plus base offset exceeding 24h.","commonSituations":"Unit-conversion mistakes (days vs hours) in hand-rolled zoneinfo replacements; clamping or sign errors producing -timedelta(1); historical LMT offsets near ±24h.","solutions":["Fix the offset computation so |offset| < timedelta(hours=24)","Check unit conversions (days*24 vs hours) in the tzinfo implementation","Clamp pathological offsets in the tzinfo hook before returning"],"exampleFix":"// before\ndef utcoffset(self, dt):\n    return timedelta(days=self.offset_hours)  # wrong unit\n// after\ndef utcoffset(self, dt):\n    return timedelta(hours=self.offset_hours)","handlingStrategy":"validation","validationCode":"from datetime import timedelta\n\ndef safe_offset(off):\n    if off is None:\n        return None\n    if not -timedelta(hours=24) < off < timedelta(hours=24):\n        raise ValueError(f'offset {off} out of range')\n    return off","typeGuard":null,"tryCatchPattern":"try:\n    dt.astimezone(tz)\nexcept ValueError as e:\n    if 'offset must be a timedelta' in str(e):\n        raise  # buggy tzinfo implementation, fix it\n    raise","preventionTips":["Double-check day-vs-hour units in offset math","Test custom tzinfo at boundary values like timedelta(hours=23, minutes=59)"],"tags":["datetime","tzinfo","valueerror","timezone"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}