python/cpython · error · ValueError
minute, second, and microsecond must be 0 when hour is 24
Error message
minute, second, and microsecond must be 0 when hour is 24
What it means
ISO 8601 allows the special value hour=24 only to mean midnight of the next day. fromisoformat's time parser returns an error_from_components flag when hour is 24 but minute, second, or microsecond is nonzero, and the datetime constructor path raises this explicit ValueError instead of the generic invalid-isoformat message.
Source
Thrown at Lib/_pydatetime.py:1986
date_components = _parse_isoformat_date(dstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
if tstr:
try:
(time_components,
became_next_day,
error_from_components,
error_from_tz) = _parse_isoformat_time(tstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
else:
if error_from_tz:
raise error_from_tz
if error_from_components:
raise ValueError("minute, second, and microsecond must be 0 when hour is 24")
if became_next_day:
year, month, day = date_components
# Only wrap day/month when it was previously valid
if 1 <= month <= 12 and day <= (days_in_month := _days_in_month(year, month)):
# Calculate midnight of the next day
day += 1
if day > days_in_month:
day = 1
month += 1
if month > 12:
month = 1
year += 1
date_components = [year, month, day]
else:
time_components = [0, 0, 0, 0, None]
return cls(*(date_components + time_components))View on GitHub (pinned to bc6749cc3b)
Solutions
- Normalize the source data: convert hour 24 with remainder into a next-day datetime with hour 0..23 plus timedelta
- Use timedelta arithmetic on datetimes instead of string hour math
- Pre-validate: if the time part starts with '24' and has nonzero lower components, rewrite before parsing
Example fix
// before
dt = datetime.fromisoformat('2024-01-01T24:30:00') # raises
# after
dt = datetime.fromisoformat('2024-01-01T00:00:00') + timedelta(days=1, minutes=30) Defensive patterns
Strategy: validation
Validate before calling
import re
HOUR24 = re.compile(r'T24:(?!00(:00(.0*)?)?$)')
if HOUR24.search(ts):
# hour 24 with nonzero components: normalize manually
dt = datetime.fromisoformat(ts.replace('T24', 'T00')) + timedelta(days=1)
else:
dt = datetime.fromisoformat(ts) Try / catch
try:
dt = datetime.fromisoformat(ts)
except ValueError as e:
if 'hour is 24' in str(e):
dt = datetime.fromisoformat(ts[:ts.index('T')] + 'T00:00:00') + timedelta(days=1)
else:
raise Prevention
- Represent end-of-day as next-day midnight with hour 0
- Use timedelta arithmetic instead of hour-24 strings
- Reject hour 24 in input validation except exact 'T24:00'
When it happens
Trigger: Strings like '2024-01-01T24:15:00' or '2024-01-01T24:00:00.000001'. Only exactly 'T24:00[:00[.000...]]' is accepted (yielding next-day midnight).
Common situations: Data feeds that normalize end-of-day as 24:00 plus a duration; scheduling systems encoding 'until midnight' as 24:xx; naive string arithmetic on hours that rolls 23:30 + 60min into '24:30'.
Related errors
- fromutc: dt.tzinfo is not self
- Argument must be a str
- Argument must be an ASCII str
- Invalid isoformat string: {date_string!r}
- Invalid isoformat string: {time_string!r}
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/1ae9dadf19ea6664.
Report an issue: GitHub.