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)