From 4f7a9006c3e5d4adfa6a4e7ddaff6e4ed2ba3c96 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sat, 20 Jun 2026 15:20:48 +0800 Subject: [PATCH 1/3] gh-151770: Fix datetime.fromisoformat() assertion on an out-of-range month with a 24:00 time The 24:00 midnight-rollover path validated only the upper month bound before calling days_in_month(), so a month below 1 reached assert(month >= 1) on a debug build (and AssertionError in the pure Python implementation). Add the missing lower bound to both so an out-of-range month consistently raises ValueError. --- Lib/_pydatetime.py | 2 +- Lib/test/datetimetester.py | 1 + .../Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst | 3 +++ Modules/_datetimemodule.c | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index b6d68f2372850a7..7c23f559f6efd94 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1987,7 +1987,7 @@ def fromisoformat(cls, date_string): if became_next_day: year, month, day = date_components # Only wrap day/month when it was previously valid - if month <= 12 and day <= (days_in_month := _days_in_month(year, month)): + 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: diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index d26e41982deb811..b01824e49e148f1 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3773,6 +3773,7 @@ def test_fromisoformat_fails_datetime_valueerror(self): "2009-04-01T12:30:90", # Second out of range "2009-04-01T12:90:45", # Minute out of range "2009-04-01T25:30:45", # Hour out of range + "2009-00-01T24:00:00", # Month out of range (below) "2009-13-01T24:00:00", # Month out of range "9999-12-31T24:00:00", # Year out of range ] diff --git a/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst b/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst new file mode 100644 index 000000000000000..55858549980d31b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst @@ -0,0 +1,3 @@ +Fix :meth:`datetime.datetime.fromisoformat` raising an unexpected error on an +out-of-range month combined with a ``24:00`` time, such as +``"2009-00-01T24:00:00"``. It now consistently raises :exc:`ValueError`. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 82b1f898a3c6741..39ca75666889e35 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -6144,7 +6144,7 @@ datetime_datetime_fromisoformat_impl(PyTypeObject *type, PyObject *string) goto error; } - if ((hour == 24) && (month <= 12)) { + if ((hour == 24) && (month >= 1) && (month <= 12)) { int d_in_month = days_in_month(year, month); if (day <= d_in_month) { if (minute == 0 && second == 0 && microsecond == 0) { From 01091e42070a8c676ed7e67fa1df78cf452e1757 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sat, 20 Jun 2026 18:01:19 +0800 Subject: [PATCH 2/3] Address review: align assert message, reword NEWS, tighten test comment --- Lib/_pydatetime.py | 2 +- Lib/test/datetimetester.py | 2 +- .../Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 7c23f559f6efd94..c1448374402de4a 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -55,7 +55,7 @@ def _days_before_year(year): def _days_in_month(year, month): "year, month -> number of days in that month in that year." - assert 1 <= month <= 12, month + assert 1 <= month <= 12, f"month must be in 1..12, not {month}" if month == 2 and _is_leap(year): return 29 return _DAYS_IN_MONTH[month] diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index b01824e49e148f1..1cbe78c1ecbfdc6 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3773,7 +3773,7 @@ def test_fromisoformat_fails_datetime_valueerror(self): "2009-04-01T12:30:90", # Second out of range "2009-04-01T12:90:45", # Minute out of range "2009-04-01T25:30:45", # Hour out of range - "2009-00-01T24:00:00", # Month out of range (below) + "2009-00-01T24:00:00", # Month below range "2009-13-01T24:00:00", # Month out of range "9999-12-31T24:00:00", # Year out of range ] diff --git a/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst b/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst index 55858549980d31b..10b3db8efa42b0f 100644 --- a/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst +++ b/Misc/NEWS.d/next/Library/2026-06-20-15-00-00.gh-issue-151770.dtiso0.rst @@ -1,3 +1,3 @@ -Fix :meth:`datetime.datetime.fromisoformat` raising an unexpected error on an -out-of-range month combined with a ``24:00`` time, such as -``"2009-00-01T24:00:00"``. It now consistently raises :exc:`ValueError`. +Fix :meth:`datetime.datetime.fromisoformat` raising :exc:`AssertionError` +instead of :exc:`ValueError` for an out-of-range month combined with a +``24:00`` time. From 0e24642006e191f35af928701eb720e074d125b8 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 20 Jun 2026 11:35:04 +0100 Subject: [PATCH 3/3] Remove additional parentheses --- Modules/_datetimemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 39ca75666889e35..979aa1beb8657b2 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -6144,7 +6144,7 @@ datetime_datetime_fromisoformat_impl(PyTypeObject *type, PyObject *string) goto error; } - if ((hour == 24) && (month >= 1) && (month <= 12)) { + if ((hour == 24) && (month >= 1 && month <= 12)) { int d_in_month = days_in_month(year, month); if (day <= d_in_month) { if (minute == 0 && second == 0 && microsecond == 0) {