From 0c3c2b1c598bea2682a044b0955c317a3d094cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E8=B1=86?= Date: Mon, 5 Oct 2026 21:29:42 +0800 Subject: [PATCH] parse_date: raise ParseError instead of leaking IndexError/ValueError parse_date indexes the numbers extracted from the input by the positions of y/m/d in the locale pattern, but never checks how many numbers were actually found. Strings with fewer than three numbers - including month-name dates like 'Jan 3, 2026' (en_US medium format) or '1. Januar 2026' - crash with a bare IndexError: >>> parse_date('Jan 3, 2026', locale='en_US') IndexError: list index out of range Out-of-range fields likewise leak datetime.date()'s ValueError, which is not the documented ParseError, so 'except ParseError' handlers miss it: >>> parse_date('01.32.2026', locale='de_DE') ValueError: day 32 must be in range 1..31 for month 1 in year 2026 Raise ParseError for both cases, matching the existing 'No numbers were found in input' behaviour. parse_time already guards its indexing (len(numbers) > 1 / > 2). --- babel/dates.py | 8 +++++++- tests/test_dates.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/babel/dates.py b/babel/dates.py index 7b626aa11..cb8e0ce47 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1316,13 +1316,19 @@ def parse_date( # FIXME: this currently only supports numbers, but should also support month # names, both in the requested locale, and english + if len(numbers) < 3: + raise ParseError(f"Expected three numbers in input, got {len(numbers)}: {string!r}") + year = numbers[indexes['Y']] year = 2000 + int(year) if len(year) == 2 else int(year) month = int(numbers[indexes['M']]) day = int(numbers[indexes['D']]) if month > 12: month, day = day, month - return datetime.date(year, month, day) + try: + return datetime.date(year, month, day) + except ValueError as error: + raise ParseError(f"Could not parse {string!r}: {error}") from error def parse_time( diff --git a/tests/test_dates.py b/tests/test_dates.py index 4f8c73907..7f8a479e8 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -465,6 +465,21 @@ def test_parse_date(): assert dates.parse_date('2004-04-01', locale='sv_SE', format='short') == date(2004, 4, 1) +def test_parse_date_raises_parse_error_instead_of_index_error(): + # Strings with fewer than three numbers cannot be matched against the + # locale's numeric date pattern; this used to leak a bare IndexError. + for string, locale in [('Jan 3, 2026', 'en_US'), ('1. Januar 2026', 'de_DE'), ('3 janvier 2026', 'fr_FR')]: + with pytest.raises(dates.ParseError): + dates.parse_date(string, locale=locale) + + +def test_parse_date_raises_parse_error_on_out_of_range_values(): + # Out-of-range fields used to leak the ValueError of datetime.date(). + for string in ['99.99.9999', '01.32.2026', '00.01.2026']: + with pytest.raises(dates.ParseError): + dates.parse_date(string, locale='de_DE') + + def test_parse_date_custom_format(): assert dates.parse_date('1.4.2024', format='dd.mm.yyyy') == date(2024, 4, 1) assert dates.parse_date('2024.4.1', format='yyyy.mm.dd') == date(2024, 4, 1)