diff --git a/Doc/library/plistlib.rst b/Doc/library/plistlib.rst index 72140e41675c350..4cfdf3655a1f0b3 100644 --- a/Doc/library/plistlib.rst +++ b/Doc/library/plistlib.rst @@ -81,6 +81,11 @@ This module defines the following functions: .. versionchanged:: 3.13 The keyword-only parameter *aware_datetime* has been added. + .. versionchanged:: next + ```` values that omit smaller units (for example ``2024-06Z``) + are now accepted, with the omitted components defaulting to the start + of the period. Previously such values raised :exc:`TypeError`. + .. function:: loads(data, *, fmt=None, dict_type=dict, aware_datetime=False) diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 93f3ef5e38af843..3a8ca370e04dab6 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -139,13 +139,11 @@ def _decode_base64(s): def _date_from_string(s, aware_datetime): order = ('year', 'month', 'day', 'hour', 'minute', 'second') + # Smaller units may be omitted; default them to the start of the period. + defaults = (1, 1, 1, 0, 0, 0) gd = _dateParser.match(s).groupdict() - lst = [] - for key in order: - val = gd[key] - if val is None: - break - lst.append(int(val)) + lst = [int(val) if (val := gd[key]) is not None else default + for key, default in zip(order, defaults)] if aware_datetime: return datetime.datetime(*lst, tzinfo=datetime.UTC) return datetime.datetime(*lst) diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index b9c261310bb5670..545a2bb947c38d8 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -938,6 +938,24 @@ def test_load_aware_datetime(self): aware_datetime=True) self.assertEqual(dt.tzinfo, datetime.UTC) + def test_load_partial_datetime(self): + # Smaller units may be omitted; missing components default to the + # start of the period. + for data, expected in [ + (b"2024Z", + datetime.datetime(2024, 1, 1)), + (b"2024-06Z", + datetime.datetime(2024, 6, 1)), + (b"2024-06-07Z", + datetime.datetime(2024, 6, 7)), + (b"2024-06-07T08Z", + datetime.datetime(2024, 6, 7, 8)), + (b"2024-06-07T08:09Z", + datetime.datetime(2024, 6, 7, 8, 9)), + ]: + with self.subTest(data=data): + self.assertEqual(plistlib.loads(data), expected) + @unittest.skipUnless("America/Los_Angeles" in zoneinfo.available_timezones(), "Can't find timezone datebase") def test_dump_aware_datetime(self): diff --git a/Misc/NEWS.d/next/Library/2026-06-10-06-36-35.gh-issue-151221.DPeEZr.rst b/Misc/NEWS.d/next/Library/2026-06-10-06-36-35.gh-issue-151221.DPeEZr.rst new file mode 100644 index 000000000000000..ae59bff28dd0e66 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-10-06-36-35.gh-issue-151221.DPeEZr.rst @@ -0,0 +1,3 @@ +Fix :mod:`plistlib` raising a confusing :exc:`TypeError` when loading a +```` that omits smaller units (for example ``2024-06Z`` or ``2024Z``). +The omitted components now default to the start of the period.