From f41660feb6e9f101e98514738b643e7bf5262080 Mon Sep 17 00:00:00 2001 From: nishad shabbir Date: Thu, 13 Aug 2026 22:00:20 +0530 Subject: [PATCH] Reject ISO week 53 in years that only have 52 weeks `iso_to_gregorian` checks that the week is between 1 and 53, but not every year has a 53rd ISO week. When it doesn't, the date arithmetic quietly lands in the next ISO year instead of failing, so `arrow.get()` returns a date from a year the caller didn't ask for: >>> arrow.get((2025, 53, 1)).date() datetime.date(2025, 12, 29) >>> _.isocalendar() datetime.IsoCalendarDate(year=2026, week=1, weekday=1) Both `datetime.date.fromisocalendar(2025, 53, 1)` and arrow's own string path, `arrow.get("2025-W53-1", "W")`, raise ValueError for the same input. Check that the computed date really falls in the requested ISO year and raise if it doesn't. Years that genuinely have 53 weeks are unaffected, which the existing (2004, 53, 6) and (2009, 53, 6) cases already cover. --- arrow/util.py | 6 ++++++ tests/test_factory.py | 5 +++++ tests/test_util.py | 12 ++++++++++++ 3 files changed, 23 insertions(+) diff --git a/arrow/util.py b/arrow/util.py index 7171d92cc..6cff8a3b9 100644 --- a/arrow/util.py +++ b/arrow/util.py @@ -101,6 +101,12 @@ def iso_to_gregorian(iso_year: int, iso_week: int, iso_day: int) -> datetime.dat year_start = fourth_jan - delta gregorian = year_start + datetime.timedelta(days=iso_day - 1, weeks=iso_week - 1) + # Only some years have 53 ISO weeks. For the ones that don't, the + # arithmetic above quietly lands in the next ISO year rather than failing, + # so reject a week the requested year does not actually have. + if gregorian.isocalendar()[0] != iso_year: + raise ValueError(f"ISO Calendar year {iso_year} has no week {iso_week}.") + return gregorian diff --git a/tests/test_factory.py b/tests/test_factory.py index 056cee412..3fe973d67 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -236,6 +236,11 @@ def test_one_arg_iso_calendar(self): with pytest.raises(ValueError): self.factory.get((2014, 7, 10)) + # 2025 has 52 ISO weeks, so week 53 is not a date in that year and + # must not silently become the first week of 2026 + with pytest.raises(ValueError): + self.factory.get((2025, 53, 1)) + def test_one_arg_other(self): with pytest.raises(TypeError): self.factory.get(object()) diff --git a/tests/test_util.py b/tests/test_util.py index 2454dac56..30cd1dc44 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -131,3 +131,15 @@ def test_iso_gregorian(self): with pytest.raises(ValueError): util.iso_to_gregorian(2013, 8, 0) + + def test_iso_gregorian_week_53(self): + # 2026 is one of the years that has 53 ISO weeks + assert util.iso_to_gregorian(2026, 53, 1) == datetime(2026, 12, 28).date() + + # 2025 only has 52, so week 53 must be rejected rather than rolling + # over into the first week of 2026 + with pytest.raises(ValueError): + util.iso_to_gregorian(2025, 53, 1) + + with pytest.raises(ValueError): + util.iso_to_gregorian(2024, 53, 1)