Skip to content

Commit 43e69f4

Browse files
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.
1 parent 3acc941 commit 43e69f4

4 files changed

Lines changed: 30 additions & 1 deletion

File tree

‎Lib/_pydatetime.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ def _parse_isoformat_date(dtstr):
396396
pos += has_sep
397397

398398
dayno = _read_isoformat_component(dtstr[pos:pos + 1], 1)
399+
pos += 1
400+
401+
if pos != len(dtstr):
402+
raise ValueError("Invalid isoformat string")
399403

400404
return list(_isoweek_to_gregorian(year, weekno, dayno))
401405
else:
@@ -406,6 +410,10 @@ def _parse_isoformat_date(dtstr):
406410

407411
pos += has_sep
408412
day = _read_isoformat_component(dtstr[pos:pos + 2], 2)
413+
pos += 2
414+
415+
if pos != len(dtstr):
416+
raise ValueError("Invalid isoformat string")
409417

410418
return [year, month, day]
411419

@@ -1984,7 +1992,7 @@ def fromisoformat(cls, date_string):
19841992
raise ValueError(
19851993
f'Invalid isoformat string: {date_string!r}') from None
19861994

1987-
if tstr:
1995+
if separator_location < len(date_string):
19881996
try:
19891997
(time_components,
19901998
became_next_day,

‎Lib/test/datetimetester.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,10 @@ def test_fromisoformat_fails(self):
22362236
'2020-W 5', # space in the week number
22372237
'2020061', # 7 chars: day slice reads a 1-character tail
22382238
'2020-W2', # 1-digit week number
2239+
# gh-158500: trailing characters must not be ignored
2240+
'2020010112', # YYYYMMDD followed by two characters
2241+
'2020W011xx', # YYYYWwwD followed by two characters
2242+
'20200101\u00e9', # 9 characters, but 10 bytes in UTF-8
22392243
'٢025-03-09', # Unicode characters
22402244
'2009\ud80002\ud80028', # Separators are surrogate codepoints
22412245
]
@@ -3977,6 +3981,11 @@ def test_fromisoformat_fails_datetime(self):
39773981
'2009-04-19T12:30:45.-05:00', # Empty fraction before offset
39783982
'2009-04-19T12:30:45.Z', # Empty fraction before Z
39793983
'2009-04-19T12:30:45,+05:00', # Empty fraction (comma) before offset
3984+
# gh-158500: a separator must be followed by a time
3985+
'2009-04-19T', # Separator without a time
3986+
'20090419T', # Separator without a time
3987+
'2009-W16-7T', # Separator without a time
3988+
'2009W167T', # Separator without a time
39803989
]
39813990

39823991
for bad_str in bad_strs:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:meth:`datetime.date.fromisoformat` now raises :exc:`ValueError` for a
2+
basic-format date followed by extra characters, such as ``'2020010112'``,
3+
instead of silently ignoring them. The pure-Python implementation of
4+
:meth:`datetime.datetime.fromisoformat` now also rejects a date followed by a
5+
single character, such as ``'2020-01-01T'``, matching the C implementation.

‎Modules/_datetimemodule.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,10 @@ parse_isoformat_date(const char *dtstr, const size_t len, int *year, int *month,
977977
iso_day = 1;
978978
}
979979

980+
if ((size_t)(p - dtstr) != len) {
981+
return -1;
982+
}
983+
980984
int rv = iso_to_ymd(*year, iso_week, iso_day, year, month, day);
981985
if (rv) {
982986
return -3 + rv;
@@ -997,6 +1001,9 @@ parse_isoformat_date(const char *dtstr, const size_t len, int *year, int *month,
9971001
if (p == NULL) {
9981002
return -1;
9991003
}
1004+
if ((size_t)(p - dtstr) != len) {
1005+
return -1;
1006+
}
10001007
return 0;
10011008
}
10021009

0 commit comments

Comments
 (0)