From 658e6760933e59cb3fd830f3c953174e41f5f51d Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Mon, 9 Nov 2026 11:18:06 +0530 Subject: [PATCH 1/3] Parse two-digit years relative to today in parse_date parse_date('3/9/99', locale='en_US') gave 2099-03-09, so a date formatted in a short pattern did not parse back. Use the same window as ICU: 80 years before and 20 years after today. --- babel/dates.py | 13 ++++++++++++- tests/test_dates.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/babel/dates.py b/babel/dates.py index e0e0e5940..7320b5a57 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1366,6 +1366,17 @@ class ParseError(ValueError): pass +def _expand_two_digit_year(year: int) -> int: + """Place a two-digit year within 80 years before and 20 years after today, as ICU does.""" + this_year = datetime.date.today().year + expanded = this_year - this_year % 100 + year + if expanded > this_year + 20: + return expanded - 100 + if expanded <= this_year - 80: + return expanded + 100 + return expanded + + def parse_date( string: str, locale: Locale | str | None = None, @@ -1438,7 +1449,7 @@ def parse_date( # names, both in the requested locale, and english year = numbers[indexes['Y']] - year = 2000 + int(year) if len(year) == 2 else int(year) + year = _expand_two_digit_year(int(year)) if len(year) == 2 else int(year) month = int(numbers[indexes['M']]) day = int(numbers[indexes['D']]) if month > 12: diff --git a/tests/test_dates.py b/tests/test_dates.py index f181a3636..e320538fb 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -696,6 +696,7 @@ def test_format_timedelta(): def test_parse_date(): assert dates.parse_date('4/1/04', locale='en_US') == date(2004, 4, 1) + assert dates.parse_date('3/9/99', locale='en_US') == date(1999, 3, 9) assert dates.parse_date('01.04.2004', locale='de_DE') == date(2004, 4, 1) assert dates.parse_date('2004-04-01', locale='sv_SE', format='short') == date(2004, 4, 1) From c4e8fb7d2b0894ca4f0f42c5b103f27b9fe2a2b9 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Mon, 9 Nov 2026 18:28:44 +0530 Subject: [PATCH 2/3] Link the ICU rule for two-digit years --- babel/dates.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/babel/dates.py b/babel/dates.py index 7320b5a57..a118dc369 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1367,7 +1367,11 @@ class ParseError(ValueError): def _expand_two_digit_year(year: int) -> int: - """Place a two-digit year within 80 years before and 20 years after today, as ICU does.""" + """Place a two-digit year within 80 years before and 20 years after today. + + This is the rule ICU documents for parsing abbreviated years, see + https://unicode-org.github.io/icu-docs/apidoc/released/icu4j/com/ibm/icu/text/SimpleDateFormat.html + """ this_year = datetime.date.today().year expanded = this_year - this_year % 100 + year if expanded > this_year + 20: From 6852e5d4a1a67f5850f76481d8d0bd36c8cf730c Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 9 Nov 2026 15:17:34 +0200 Subject: [PATCH 3/3] Adjust tests --- babel/dates.py | 3 +++ tests/test_dates.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/babel/dates.py b/babel/dates.py index a118dc369..05d866411 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1410,6 +1410,9 @@ def parse_date( >>> parse_date('01.04.04', locale='de_DE', format='short') datetime.date(2004, 4, 1) + Two-digit years are placed within 80 years before and 20 years after + the current year. + :param string: the string containing the date :param locale: a `Locale` object or a locale identifier :param locale: a `Locale` object or a locale identifier. Defaults to the system time locale. diff --git a/tests/test_dates.py b/tests/test_dates.py index e320538fb..2d4141f1c 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -696,11 +696,23 @@ def test_format_timedelta(): def test_parse_date(): assert dates.parse_date('4/1/04', locale='en_US') == date(2004, 4, 1) - assert dates.parse_date('3/9/99', locale='en_US') == date(1999, 3, 9) assert dates.parse_date('01.04.2004', locale='de_DE') == date(2004, 4, 1) assert dates.parse_date('2004-04-01', locale='sv_SE', format='short') == date(2004, 4, 1) +@pytest.mark.parametrize(('today', 'yy', 'expected'), [ + ('2026-11-09', '99', 1999), + ('2026-11-09', '46', 2046), + ('2026-11-09', '47', 1947), + ('2090-06-01', '05', 2105), + ('2090-06-01', '10', 2110), + ('2090-06-01', '11', 2011), +]) +def test_parse_date_two_digit_year(today, yy, expected): + with freezegun.freeze_time(today): + assert dates.parse_date(f'1/1/{yy}', locale='en_US').year == expected + + 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)