From 3f8955f1abfa5af957e604721fd37e9debeffb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Thu, 2 Jul 2026 12:01:56 +0800 Subject: [PATCH 1/3] gh-152847: Reject non-digit day-of-year in pure-Python zoneinfo POSIX TZ rules The J and n day-of-year branches of _parse_dst_start_end() fell through to a bare int(date), accepting input the C accelerator rejects (for example J1_0, which int() reads as day 10, silently building a different zone). Guard the branch with an re.ASCII digit match mirroring the C parser's parse_digits(1, 3), so both implementations agree. --- Lib/test/test_zoneinfo/test_zoneinfo.py | 21 +++++++++++++++++++ Lib/zoneinfo/_zoneinfo.py | 2 ++ ...-07-02-12-00-00.gh-issue-152847.PitKqc.rst | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 813b56501308f9e..bbc3ea99f395d7f 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1111,6 +1111,9 @@ def test_extreme_tzstr(self): "AAA4BBB,J1/2,J1/14", "AAA4BBB,J20/2,J365/2", "AAA4BBB,J365/2,J365/14", + # Leading-zero day-of-year + "AAA4BBB,J001/2,J065/2", + "AAA4BBB,001/2,065/2", # Extreme transition hour "AAA4BBB,J60/167,J300/2", "AAA4BBB,J60/+167,J300/2", @@ -1209,6 +1212,15 @@ def test_invalid_tzstr(self): # Invalid julian offset "AAA4BBB,J0/2,J20/2", "AAA4BBB,J20/2,J366/2", + # gh-152847: non-digit day-of-year (e.g. J1_0) + "AAA4BBB,J1_0,J300/2", + "AAA4BBB,J60/2,J30_0/2", + "AAA4BBB,1_0,J300/2", + "AAA4BBB,J+1,J300/2", + "AAA4BBB,J 1,J300/2", + "AAA4BBB, 1,J300/2", + "AAA4BBB,J0001,J300/2", + "AAA4BBB,0001,J300/2", # Invalid transition time "AAA4BBB,J60/2/3,J300/2", "AAA4BBB,J60/2,J300/2/3", @@ -1248,6 +1260,15 @@ def test_invalid_tzstr_non_ascii_abbr(self): with self.assertRaisesRegex(ValueError, expected): self.zone_from_tzstr(tzstr, encoding="utf-8") + def test_invalid_tzstr_non_ascii_dst_date(self): + tzstr = "AAA4BBB,J١,J300/2" + if self.module is py_zoneinfo: + expected = re.escape(tzstr) + else: + expected = re.escape(repr(tzstr.encode("utf-8"))) + with self.assertRaisesRegex(ValueError, expected): + self.zone_from_tzstr(tzstr, encoding="utf-8") + @classmethod def _populate_test_cases(cls): # This method uses a somewhat unusual style in that it populates the diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py index 90cf2bbf8f5d0d1..48154775568b6dc 100644 --- a/Lib/zoneinfo/_zoneinfo.py +++ b/Lib/zoneinfo/_zoneinfo.py @@ -720,6 +720,8 @@ def _parse_dst_start_end(dststr): else: n_is_julian = False + if re.fullmatch(r"\d{1,3}", date, re.ASCII) is None: + raise ValueError(f"Invalid dst start/end date: {dststr}") doy = int(date) offset = _DayOffset(doy, n_is_julian) diff --git a/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst b/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst new file mode 100644 index 000000000000000..2c4d9c0960ad9a3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst @@ -0,0 +1,3 @@ +Fix the pure-Python :mod:`zoneinfo` parser accepting non-digit characters in +the day-of-year field of a POSIX TZ transition rule (such as ``J1_0``), which +the C implementation rejects. Patch by tonghuaroot. From 76eebcaf70eb829e4e44c183c4ebae239b76f46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Thu, 2 Jul 2026 12:05:45 +0800 Subject: [PATCH 2/3] gh-152847: Trim the NEWS entry --- .../Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst b/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst index 2c4d9c0960ad9a3..3f20d78a201e84c 100644 --- a/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst +++ b/Misc/NEWS.d/next/Library/2026-07-02-12-00-00.gh-issue-152847.PitKqc.rst @@ -1,3 +1,3 @@ -Fix the pure-Python :mod:`zoneinfo` parser accepting non-digit characters in -the day-of-year field of a POSIX TZ transition rule (such as ``J1_0``), which -the C implementation rejects. Patch by tonghuaroot. +Reject a POSIX TZ transition rule with non-digit characters in the +day-of-year field in the pure-Python :mod:`zoneinfo` parser. Patch by +tonghuaroot. From e8e43b56f0db8ea75dc40dcef6c277abdbb13ad2 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Thu, 2 Jul 2026 13:55:33 +0200 Subject: [PATCH 3/3] Remove useless detail from comment --- Lib/test/test_zoneinfo/test_zoneinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index bbc3ea99f395d7f..a10f434eb27590d 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1212,7 +1212,7 @@ def test_invalid_tzstr(self): # Invalid julian offset "AAA4BBB,J0/2,J20/2", "AAA4BBB,J20/2,J366/2", - # gh-152847: non-digit day-of-year (e.g. J1_0) + # gh-152847: non-digit day-of-year "AAA4BBB,J1_0,J300/2", "AAA4BBB,J60/2,J30_0/2", "AAA4BBB,1_0,J300/2",