From 7d56a0164425633e2d4069857cf0e1d3c47088b0 Mon Sep 17 00:00:00 2001 From: Gustaf Date: Wed, 21 May 2025 11:28:48 -0400 Subject: [PATCH 01/15] added tests for obscure year inputs to parsedate_to_datetime --- .../test_email/test_parsedate_to_datetime.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Lib/test/test_email/test_parsedate_to_datetime.py diff --git a/Lib/test/test_email/test_parsedate_to_datetime.py b/Lib/test/test_email/test_parsedate_to_datetime.py new file mode 100644 index 000000000000000..4ab13792265b029 --- /dev/null +++ b/Lib/test/test_email/test_parsedate_to_datetime.py @@ -0,0 +1,17 @@ +# Test to see if parsedate_to_datetime returns the correct year for different digit numbers, adhering to the RFC2822 spec + +import unittest +from email.utils import parsedate_to_datetime + +class ParsedateToDatetimeTest(unittest.TestCase): + def test(self): + expectations = { + "Sat, 15 Aug 0001 23:12:09 +0500": "0001", + "Thu, 1 Sep 1 23:12:09 +0800": "0001", + "Thu, 7 Oct 123 23:12:09 +0500": "0123", + } + for input_string, output_string in expectations.items(): + self.assertEqual(str(parsedate_to_datetime(input_string))[:4], output_string) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From e38deda46ecb6c64a3047d6f27d11c39ac435a9d Mon Sep 17 00:00:00 2001 From: Gustaf Date: Wed, 21 May 2025 11:29:00 -0400 Subject: [PATCH 02/15] updated test_email to correctly assert 1 digit year --- Lib/test/test_email/test_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index d2c2261edbe04e1..316cd08733310d3 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -3191,7 +3191,7 @@ def test_parsedate_y2k(self): """ self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 2003 13:47:26 -0800')) + utils.parsedate_tz('25 Feb 3 13:47:26 -0800')) self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'), utils.parsedate_tz('25 Feb 1971 13:47:26 -0800')) From d71c0f875a55f7d60f469d6a0bb7dc02c37b5588 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 15:32:05 +0000 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst new file mode 100644 index 000000000000000..90932a35c1ce7ce --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst @@ -0,0 +1,2 @@ +Fixed _parsedate_tz to not evaluate 1 digit years as as 4-digit years, adhering to RFC 2855 4.3 +Contributed by Gustaf Gyllensporre. From cc4ec42076021219896d605922e01c1e73ae6ef1 Mon Sep 17 00:00:00 2001 From: Gustaf <79180496+GGyll@users.noreply.github.com> Date: Wed, 21 May 2025 17:38:22 +0200 Subject: [PATCH 04/15] Added newline at end of test_parsedate_to_datetime.py --- Lib/test/test_email/test_parsedate_to_datetime.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_email/test_parsedate_to_datetime.py b/Lib/test/test_email/test_parsedate_to_datetime.py index 4ab13792265b029..4ae005061479a57 100644 --- a/Lib/test/test_email/test_parsedate_to_datetime.py +++ b/Lib/test/test_email/test_parsedate_to_datetime.py @@ -14,4 +14,5 @@ def test(self): self.assertEqual(str(parsedate_to_datetime(input_string))[:4], output_string) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() + From 7db242da682b72935a62b64d1f1d423f1220f5f6 Mon Sep 17 00:00:00 2001 From: Gustaf <79180496+GGyll@users.noreply.github.com> Date: Wed, 21 May 2025 17:57:20 +0200 Subject: [PATCH 05/15] removed trailing whitespace in parsedate_to_datetime --- Lib/test/test_email/test_parsedate_to_datetime.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_email/test_parsedate_to_datetime.py b/Lib/test/test_email/test_parsedate_to_datetime.py index 4ae005061479a57..4f0f64e8b53594e 100644 --- a/Lib/test/test_email/test_parsedate_to_datetime.py +++ b/Lib/test/test_email/test_parsedate_to_datetime.py @@ -15,4 +15,3 @@ def test(self): if __name__ == '__main__': unittest.main() - From d5a9948addd01685a8738bc8176063ccd38c3687 Mon Sep 17 00:00:00 2001 From: Gustaf Date: Thu, 17 Jul 2025 21:53:37 +0200 Subject: [PATCH 06/15] fixed 3-digit year edge case --- Lib/email/_parseaddr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py index e311948cb09a7d6..2a98b66cda57926 100644 --- a/Lib/email/_parseaddr.py +++ b/Lib/email/_parseaddr.py @@ -150,7 +150,7 @@ def _parsedate_tz(data): # mandated a 4-digit yy, and RFC 5322 (which obsoletes RFC 2822) continues # this requirement. For more information, see the documentation for # the time module. - if yy < 100: + if yy < 999: # The year is between 1969 and 1999 (inclusive). if yy > 68: yy += 1900 From 55420b64954887767c6b0e756bf35eb6c2ec9762 Mon Sep 17 00:00:00 2001 From: Gustaf Date: Thu, 17 Jul 2025 21:55:22 +0200 Subject: [PATCH 07/15] reset year check to previous value --- Lib/test/test_email/test_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 316cd08733310d3..d2c2261edbe04e1 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -3191,7 +3191,7 @@ def test_parsedate_y2k(self): """ self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 3 13:47:26 -0800')) + utils.parsedate_tz('25 Feb 2003 13:47:26 -0800')) self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'), utils.parsedate_tz('25 Feb 1971 13:47:26 -0800')) From cbfabab127569ced819d8ccdff8df26122fa941d Mon Sep 17 00:00:00 2001 From: Gustaf Date: Thu, 17 Jul 2025 21:56:29 +0200 Subject: [PATCH 08/15] moved ParsedateToDatetimeTest --- .../test_email/test_parsedate_to_datetime.py | 17 ----------------- Lib/test/test_email/test_utils.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 17 deletions(-) delete mode 100644 Lib/test/test_email/test_parsedate_to_datetime.py diff --git a/Lib/test/test_email/test_parsedate_to_datetime.py b/Lib/test/test_email/test_parsedate_to_datetime.py deleted file mode 100644 index 4f0f64e8b53594e..000000000000000 --- a/Lib/test/test_email/test_parsedate_to_datetime.py +++ /dev/null @@ -1,17 +0,0 @@ -# Test to see if parsedate_to_datetime returns the correct year for different digit numbers, adhering to the RFC2822 spec - -import unittest -from email.utils import parsedate_to_datetime - -class ParsedateToDatetimeTest(unittest.TestCase): - def test(self): - expectations = { - "Sat, 15 Aug 0001 23:12:09 +0500": "0001", - "Thu, 1 Sep 1 23:12:09 +0800": "0001", - "Thu, 7 Oct 123 23:12:09 +0500": "0123", - } - for input_string, output_string in expectations.items(): - self.assertEqual(str(parsedate_to_datetime(input_string))[:4], output_string) - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py index c9d09098b502f9d..c3739dbcd3633c3 100644 --- a/Lib/test/test_email/test_utils.py +++ b/Lib/test/test_email/test_utils.py @@ -186,5 +186,18 @@ def test_formatdate_with_localtime(self): string = utils.formatdate(timeval, localtime=True) self.assertEqual(string, 'Thu, 01 Dec 2011 18:00:00 +0300') +# Issue #126845: Some edge cases seem to differ from RFC28222 spec +class ParsedateToDatetimeTest(unittest.TestCase): + def test_year_parsing_edge_cases(self): + expectations = { + "Sat, 15 Aug 0001 23:12:09 +0500": "2001", + "Thu, 1 Sep 1 23:12:09 +0800": "2001", + "Thu, 7 Oct 123 23:12:09 +0500": "2023", + "Tue, 17 Nov 2026 12:12:09 +0500": "2026", + } + for input_string, output_string in expectations.items(): + with self.subTest(input_string=input_string): + self.assertEqual(str(utils.parsedate_to_datetime(input_string))[:4], output_string) + if __name__ == '__main__': unittest.main() From 3747dade6b3071756acf4022d78a81e554716800 Mon Sep 17 00:00:00 2001 From: Gustaf Date: Tue, 2 Jun 2026 18:47:27 +0200 Subject: [PATCH 09/15] change yy to < 1000 --- Lib/email/_parseaddr.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py index 2a98b66cda57926..8f17a886b5a3035 100644 --- a/Lib/email/_parseaddr.py +++ b/Lib/email/_parseaddr.py @@ -146,15 +146,15 @@ def _parsedate_tz(data): return None # Check for a yy specified in two-digit format, then convert it to the # appropriate four-digit format, according to the POSIX standard. RFC 822 - # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822) already - # mandated a 4-digit yy, and RFC 5322 (which obsoletes RFC 2822) continues - # this requirement. For more information, see the documentation for - # the time module. - if yy < 999: - # The year is between 1969 and 1999 (inclusive). + # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822) + # mandated a 4-digit yyyy, and RFC 5322 (which obsoletes RFC 2822) + # continues this requirement. Two digit years between 69 and 99 inclusive, + # and three digit years, are to be interpreted as 1900s dates, while those + # between 0 and 68 are to be treated as 2000s dates. + # (https://datatracker.ietf.org/doc/html/rfc5322#section-4.3) + if yy < 1000: if yy > 68: yy += 1900 - # The year is between 2000 and 2068 (inclusive). else: yy += 2000 tzoffset = None From e10af2dffbff9aa37c5277bbebb6b417cc681eb8 Mon Sep 17 00:00:00 2001 From: Gustaf Date: Tue, 2 Jun 2026 18:47:39 +0200 Subject: [PATCH 10/15] updated test edge cases --- Lib/test/test_email/test_utils.py | 37 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py index c3739dbcd3633c3..9141230995cb9af 100644 --- a/Lib/test/test_email/test_utils.py +++ b/Lib/test/test_email/test_utils.py @@ -77,6 +77,30 @@ def test_parsedate_to_datetime_with_invalid_raises_valueerror(self): with self.subTest(dtstr=dtstr): self.assertRaises(ValueError, utils.parsedate_to_datetime, dtstr) + def test_parsedate_to_datetime_year_edge_cases(self): + expectations = { + # Various short-year formats that get expanded + "Sat, 15 Aug 0001 23:12:09 +0500": "2001", + "Thu, 1 Sep 1 23:12:09 +0800": "2001", + "Thu, 7 Oct 123 23:12:09 +0500": "2023", + "Tue, 17 Nov 2026 12:12:09 +0500": "2026", + # RFC 5322 section 4.3 boundaries for 2-digit years + "Mon, 1 Jan 0 00:00:00 +0000": "2000", + "Mon, 1 Jan 68 00:00:00 +0000": "2068", + "Mon, 1 Jan 69 00:00:00 +0000": "1969", + "Mon, 1 Jan 99 00:00:00 +0000": "1999", + # 3-digit year boundary + "Mon, 1 Jan 999 00:00:00 +0000": "2899", + # Pre-1900 four-digit year: illegal per RFC but we accept it + "Mon, 1 Jan 1000 00:00:00 +0000": "1000", + } + for input_string, expected_year in expectations.items(): + with self.subTest(input_string=input_string): + self.assertEqual( + str(utils.parsedate_to_datetime(input_string))[:4], + expected_year, + ) + class LocaltimeTests(unittest.TestCase): def test_localtime_is_tz_aware_daylight_true(self): @@ -186,18 +210,5 @@ def test_formatdate_with_localtime(self): string = utils.formatdate(timeval, localtime=True) self.assertEqual(string, 'Thu, 01 Dec 2011 18:00:00 +0300') -# Issue #126845: Some edge cases seem to differ from RFC28222 spec -class ParsedateToDatetimeTest(unittest.TestCase): - def test_year_parsing_edge_cases(self): - expectations = { - "Sat, 15 Aug 0001 23:12:09 +0500": "2001", - "Thu, 1 Sep 1 23:12:09 +0800": "2001", - "Thu, 7 Oct 123 23:12:09 +0500": "2023", - "Tue, 17 Nov 2026 12:12:09 +0500": "2026", - } - for input_string, output_string in expectations.items(): - with self.subTest(input_string=input_string): - self.assertEqual(str(utils.parsedate_to_datetime(input_string))[:4], output_string) - if __name__ == '__main__': unittest.main() From 213e4753152e502d672d11244e31ca5932d318f8 Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Tue, 2 Jun 2026 15:01:22 -0400 Subject: [PATCH 11/15] Update 2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst --- .../2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst index 90932a35c1ce7ce..cee5589e777e7f4 100644 --- a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst +++ b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst @@ -1,2 +1,7 @@ -Fixed _parsedate_tz to not evaluate 1 digit years as as 4-digit years, adhering to RFC 2855 4.3 -Contributed by Gustaf Gyllensporre. +Fixed the :mod:`email` parsing of three digit dates to +conform to :rfc:`5322`: three digit dates were previously +turned in to non-conformant four digit dates with a +leading `0`. Now, per the RFC section 4.3, `1990` is added +to such dates to form compliant four digit years. + +Contributed by Gustaf Gyllensporre. From 0be66129589cd9d8ff451c2960f6b6023c610ac8 Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Tue, 2 Jun 2026 16:15:38 -0400 Subject: [PATCH 12/15] Update 2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst --- .../Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst index cee5589e777e7f4..0730665656478e8 100644 --- a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst +++ b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst @@ -1,7 +1,7 @@ -Fixed the :mod:`email` parsing of three digit dates to +Fixed the :mod:`email` module parsing of three digit dates to conform to :rfc:`5322`: three digit dates were previously turned in to non-conformant four digit dates with a -leading `0`. Now, per the RFC section 4.3, `1990` is added +leading ``0``. Now, per the RFC section 4.3, `1990` is added to such dates to form compliant four digit years. Contributed by Gustaf Gyllensporre. From 0907f21984c3d99382d8b3f7a8e3a889ff15eb2f Mon Sep 17 00:00:00 2001 From: "R. David Murray" Date: Tue, 2 Jun 2026 16:19:00 -0400 Subject: [PATCH 13/15] Update 2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst --- .../next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst index 0730665656478e8..a1c5db9e1110f08 100644 --- a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst +++ b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst @@ -1,7 +1,7 @@ Fixed the :mod:`email` module parsing of three digit dates to conform to :rfc:`5322`: three digit dates were previously turned in to non-conformant four digit dates with a -leading ``0``. Now, per the RFC section 4.3, `1990` is added +leading ``0``. Now, per the RFC section 4.3, ``1990`` is added to such dates to form compliant four digit years. Contributed by Gustaf Gyllensporre. From 2896ee9fa0f20febc7d5695dd87eb97a5d7a6ddc Mon Sep 17 00:00:00 2001 From: R David Murray Date: Wed, 3 Jun 2026 13:54:24 -0400 Subject: [PATCH 14/15] Normalize news file line endings. --- .../2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst index a1c5db9e1110f08..88ce20f102bd92e 100644 --- a/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst +++ b/Misc/NEWS.d/next/Library/2025-05-21-15-32-04.gh-issue-126845.QhEGg6.rst @@ -1,7 +1,7 @@ -Fixed the :mod:`email` module parsing of three digit dates to -conform to :rfc:`5322`: three digit dates were previously -turned in to non-conformant four digit dates with a -leading ``0``. Now, per the RFC section 4.3, ``1990`` is added -to such dates to form compliant four digit years. - -Contributed by Gustaf Gyllensporre. +Fixed the :mod:`email` module parsing of three digit dates to +conform to :rfc:`5322`: three digit dates were previously +turned in to non-conformant four digit dates with a +leading ``0``. Now, per the RFC section 4.3, ``1990`` is added +to such dates to form compliant four digit years. + +Contributed by Gustaf Gyllensporre. From d621858815ce80b01e326639a904134125df6147 Mon Sep 17 00:00:00 2001 From: R David Murray Date: Wed, 3 Jun 2026 13:56:38 -0400 Subject: [PATCH 15/15] Add test for year 100. --- Lib/test/test_email/test_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py index 9141230995cb9af..4d575f8beb922ba 100644 --- a/Lib/test/test_email/test_utils.py +++ b/Lib/test/test_email/test_utils.py @@ -89,7 +89,8 @@ def test_parsedate_to_datetime_year_edge_cases(self): "Mon, 1 Jan 68 00:00:00 +0000": "2068", "Mon, 1 Jan 69 00:00:00 +0000": "1969", "Mon, 1 Jan 99 00:00:00 +0000": "1999", - # 3-digit year boundary + # 3-digit year boundaries + "Mon, 1 Jan 100 00:00:00 +0000": "2000", "Mon, 1 Jan 999 00:00:00 +0000": "2899", # Pre-1900 four-digit year: illegal per RFC but we accept it "Mon, 1 Jan 1000 00:00:00 +0000": "1000",