Skip to content
18 changes: 13 additions & 5 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,22 @@ def _parse_isoformat_date(dtstr):
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
if len(dtstr) not in (7, 8, 10):
raise ValueError("Invalid isoformat string")
year = int(dtstr[0:4])
if not dtstr.isascii():
raise ValueError(f"Invalid isoformat string: {dtstr!r}")
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
def _read(s, n):
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
# dtstr is ASCII, so isdigit() matches only ASCII digits.
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
if len(s) != n or not s.isdigit():
raise ValueError(f"Invalid isoformat string: {dtstr!r}")
return int(s)

year = _read(dtstr[0:4], 4)
has_sep = dtstr[4] == '-'

pos = 4 + has_sep
if dtstr[pos:pos + 1] == "W":
# YYYY-?Www-?D?
pos += 1
weekno = int(dtstr[pos:pos + 2])
weekno = _read(dtstr[pos:pos + 2], 2)
pos += 2

dayno = 1
Expand All @@ -377,17 +385,17 @@ def _parse_isoformat_date(dtstr):

pos += has_sep

dayno = int(dtstr[pos:pos + 1])
dayno = _read(dtstr[pos:pos + 1], 1)

return list(_isoweek_to_gregorian(year, weekno, dayno))
else:
month = int(dtstr[pos:pos + 2])
month = _read(dtstr[pos:pos + 2], 2)
pos += 2
if (dtstr[pos:pos + 1] == "-") != has_sep:
raise ValueError("Inconsistent use of dash separator")

pos += has_sep
day = int(dtstr[pos:pos + 2])
day = _read(dtstr[pos:pos + 2], 2)

return [year, month, day]

Expand Down
10 changes: 10 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,16 @@ def test_fromisoformat_fails(self):
'10000-W25-1', # Invalid year
'2020-W25-0', # Invalid day-of-week
'2020-W25-8', # Invalid day-of-week
# gh-152204: each fixed-width field must be exactly N ASCII digits
'2020+12', # '+' accepted in a basic-format field
'2020 12', # space accepted in a basic-format field
'+020-06-15', # leading sign in the year
'202012+9', # '+' in the day field
'2020-W 5', # space in the week day-of-week field
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
'2020061', # 7 chars: day slice reads a 1-character tail
'2020123', # 7 chars: day slice reads a 1-character tail
'9999121', # 7 chars: day slice reads a 1-character tail
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
'2020-W2', # 1-digit week number
'٢025-03-09' # Unicode characters
Comment thread
StanFromIreland marked this conversation as resolved.
Resolved
'2009\ud80002\ud80028', # Separators are surrogate codepoints
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix the pure-Python implementation of :meth:`datetime.date.fromisoformat`
silently mis-parsing some malformed ISO 8601 basic-format dates (for example
``'2020+12'`` or ``'2020061'``). Each fixed-width field is now required to be
exactly *N* ASCII digits before conversion, matching the C implementation,
which already rejected these inputs.
Loading