Bug report
datetime.fromisoformat() reaches days_in_month() with month == 0 when the string combines an out-of-range (below 1) month with a 24:00 time, tripping a debug-build assertion. On a release build the same input raises ValueError, so the behaviour is inconsistent between build configurations.
Reproduction
>>> from datetime import datetime >>> datetime.fromisoformat("2009-00-01T24:00:00")
On a --with-pydebug build (C implementation):
Assertion failed: (month >= 1), function days_in_month, file Modules/_datetimemodule.c, line 441.
(SIGABRT). The pure-Python implementation in Lib/_pydatetime.py raises AssertionError on the same input. On a release / -DNDEBUG build, both implementations correctly raise ValueError: month must be in 1..12, not 0.
Root cause
The 24:00 midnight-rollover guard validates the upper month bound but not the lower one before calling days_in_month():
Modules/_datetimemodule.c,datetime_datetime_fromisoformat_impl:if ((hour == 24) && (month <= 12))Lib/_pydatetime.py,datetime.fromisoformat:if month <= 12 and day <= (days_in_month := _days_in_month(year, month))
The parser does not range-check the month before this point, so month == 0 flows into days_in_month(year, 0) and hits assert(month >= 1). The matching upper-bound input "2009-13-01T24:00:00" is already rejected by the month <= 12 check; only the lower bound is missing.
Suggested fix
Add the month >= 1 lower bound to the guard in both implementations (mirroring check_date_args's existing month < 1 || month > 12 validation), so the 24:00 path is only taken for an in-range month and the normal ValueError is raised otherwise. I'll open a PR.
This was found while adding a fuzz harness for fromisoformat.
Linked PRs
- gh-151770: Fix datetime.fromisoformat() assertion on an out-of-range month with a 24:00 time #151771
- [3.15] gh-151770: Fix
datetime.fromisoformat()on an out-of-range month w/ a 24:00 time (GH-151771) #151809 - [3.14] gh-151770: Fix
datetime.fromisoformat()on an out-of-range month w/ a 24:00 time (GH-151771) #151810