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)