{"record":{"id":"33a342e0267596c4","repo":"python/cpython","slug":"hour-must-be-in-0-23-not-hour","errorCode":null,"errorMessage":"hour must be in 0..23, not {hour}","messagePattern":"hour must be in 0\\.\\.23, not (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":587,"sourceCode":"    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)\n    minute = _index(minute)\n    second = _index(second)\n    microsecond = _index(microsecond)\n    if not 0 <= hour <= 23:\n        raise ValueError(f\"hour must be in 0..23, not {hour}\")\n    if not 0 <= minute <= 59:\n        raise ValueError(f\"minute must be in 0..59, not {minute}\")\n    if not 0 <= second <= 59:\n        raise ValueError(f\"second must be in 0..59, not {second}\")\n    if not 0 <= microsecond <= 999999:\n        raise ValueError(f\"microsecond must be in 0..999999, not {microsecond}\")\n    if fold not in (0, 1):\n        raise ValueError(f\"fold must be either 0 or 1, not {fold}\")\n    return hour, minute, second, microsecond, fold\n\ndef _check_tzinfo_arg(tz):\n    if tz is not None and not isinstance(tz, tzinfo):\n        raise TypeError(\n            \"tzinfo argument must be None or of a tzinfo subclass, \"\n            f\"not {type(tz).__name__!r}\"\n        )\n\ndef _divide_and_round(a, b):","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L569-L605","documentation":"Raised by _check_time_fields() when the hour argument to time()/datetime() is not in 0..23 (after __index__ coercion). Python time objects use a 24-hour representation, so 24 or negative hours are invalid — unlike some locales/APIs where '24:00' is a valid end-of-day marker.","triggerScenarios":"time(24, 0, 0); datetime(2024, 1, 1, 25); converting '24:00' from ISO 8601 strings that permit it; hour arithmetic like (h + duration) % 25 bugs.","commonSituations":"Parsing ISO 8601 durations/intervals that legally use 24:00; spreadsheet or business-software exports with 24:00; forgetting to modulo hour arithmetic after adding offsets.","solutions":["Normalize '24:00' to time(0, 0) of the next day when parsing","Apply % 24 and carry to days when doing hour arithmetic","Validate 0 <= hour <= 23 on parsed input before construction"],"exampleFix":"// before\nt = time(24, 0)  # ValueError\n// after\nd = datetime(y, m, d, 0, 0) + timedelta(days=1)  # represents 24:00 as next midnight","handlingStrategy":"validation","validationCode":"if not 0 <= hour <= 23:\n    raise ValueError('hour must be 0..23')","typeGuard":"def valid_hour(h) -> bool:\n    return 0 <= h <= 23","tryCatchPattern":null,"preventionTips":["Map ISO 8601 '24:00' to next-day midnight instead of time(24, 0)","Use % 24 with day carry after hour arithmetic"],"tags":["datetime","valueerror","validation","time"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}