From 43e69f400d6bdffb5743b106e4e075e9f2f9c185 Mon Sep 17 00:00:00 2001
From: iamsaugatpandey
Date: Mon, 26 Oct 2026 17:41:07 -0400
Subject: [PATCH] gh-158500: Reject trailing characters in date.fromisoformat()
date.fromisoformat() accepts strings of length 7, 8 or 10, but the date
parsers never checked that the whole string was consumed. A 10-character
string without a "-" after the year was parsed as an 8-character basic
format date and its last two characters were dropped, so
date.fromisoformat('2020010112') returned date(2020, 1, 1).
Check that the parser stopped exactly at the end of the date in both the
C and pure-Python implementations.
The pure-Python datetime.fromisoformat() had a similar problem: a date
followed by a single character (such as '2020-01-01T') was accepted
because the empty time part was skipped. Parse the time whenever
something follows the date, as the C implementation does.
---
Lib/_pydatetime.py | 10 +++++++++-
Lib/test/datetimetester.py | 9 +++++++++
.../2026-10-26-21-37-42.gh-issue-158500.zxyuq5.rst | 5 +++++
Modules/_datetimemodule.c | 7 +++++++
4 files changed, 30 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-10-26-21-37-42.gh-issue-158500.zxyuq5.rst
diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py
index 076183139b28ae..941bb54f04a928 100644
--- a/Lib/_pydatetime.py
+++ b/Lib/_pydatetime.py
@@ -396,6 +396,10 @@ def _parse_isoformat_date(dtstr):
pos += has_sep
dayno = _read_isoformat_component(dtstr[pos:pos + 1], 1)
+ pos += 1
+
+ if pos != len(dtstr):
+ raise ValueError("Invalid isoformat string")
return list(_isoweek_to_gregorian(year, weekno, dayno))
else:
@@ -406,6 +410,10 @@ def _parse_isoformat_date(dtstr):
pos += has_sep
day = _read_isoformat_component(dtstr[pos:pos + 2], 2)
+ pos += 2
+
+ if pos != len(dtstr):
+ raise ValueError("Invalid isoformat string")
return [year, month, day]
@@ -1984,7 +1992,7 @@ def fromisoformat(cls, date_string):
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
- if tstr:
+ if separator_location < len(date_string):
try:
(time_components,
became_next_day,
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 53285d918c17ca..a9970031cd374c 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -2236,6 +2236,10 @@ def test_fromisoformat_fails(self):
'2020-W 5', # space in the week number
'2020061', # 7 chars: day slice reads a 1-character tail
'2020-W2', # 1-digit week number
+ # gh-158500: trailing characters must not be ignored
+ '2020010112', # YYYYMMDD followed by two characters
+ '2020W011xx', # YYYYWwwD followed by two characters
+ '20200101\u00e9', # 9 characters, but 10 bytes in UTF-8
'٢025-03-09', # Unicode characters
'2009\ud80002\ud80028', # Separators are surrogate codepoints
]
@@ -3977,6 +3981,11 @@ def test_fromisoformat_fails_datetime(self):
'2009-04-19T12:30:45.-05:00', # Empty fraction before offset
'2009-04-19T12:30:45.Z', # Empty fraction before Z
'2009-04-19T12:30:45,+05:00', # Empty fraction (comma) before offset
+ # gh-158500: a separator must be followed by a time
+ '2009-04-19T', # Separator without a time
+ '20090419T', # Separator without a time
+ '2009-W16-7T', # Separator without a time
+ '2009W167T', # Separator without a time
]
for bad_str in bad_strs:
diff --git a/Misc/NEWS.d/next/Library/2026-10-26-21-37-42.gh-issue-158500.zxyuq5.rst b/Misc/NEWS.d/next/Library/2026-10-26-21-37-42.gh-issue-158500.zxyuq5.rst
new file mode 100644
index 00000000000000..52fb55d859fda7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-26-21-37-42.gh-issue-158500.zxyuq5.rst
@@ -0,0 +1,5 @@
+:meth:`datetime.date.fromisoformat` now raises :exc:`ValueError` for a
+basic-format date followed by extra characters, such as ``'2020010112'``,
+instead of silently ignoring them. The pure-Python implementation of
+:meth:`datetime.datetime.fromisoformat` now also rejects a date followed by a
+single character, such as ``'2020-01-01T'``, matching the C implementation.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 959abcf5e421cf..380d8b435e73d6 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -977,6 +977,10 @@ parse_isoformat_date(const char *dtstr, const size_t len, int *year, int *month,
iso_day = 1;
}
+ if ((size_t)(p - dtstr) != len) {
+ return -1;
+ }
+
int rv = iso_to_ymd(*year, iso_week, iso_day, year, month, day);
if (rv) {
return -3 + rv;
@@ -997,6 +1001,9 @@ parse_isoformat_date(const char *dtstr, const size_t len, int *year, int *month,
if (p == NULL) {
return -1;
}
+ if ((size_t)(p - dtstr) != len) {
+ return -1;
+ }
return 0;
}