diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 50e21a12335611..101f4ffdfefdcd 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -452,7 +452,7 @@ def _parse_hh_mm_ss_ff(tstr): return time_comps -def _parse_isoformat_time(tstr): +def _parse_isoformat_time(tstr, is_expanded=False): # Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]] len_str = len(tstr) if len_str < 2: @@ -493,6 +493,15 @@ def _parse_isoformat_time(tstr): if len(tzstr) in (0, 1, 3) or tstr[tz_pos-1] == 'Z': raise ValueError("Malformed time zone string") + if is_expanded and ":" not in tzstr and len(tzstr) > 2: + import warnings + warnings.warn( + "Support for partially expanded formats are deprecated in " + "accordance with ISO 8601:2 and will be removed in 3.15", + DeprecationWarning, + stacklevel=3, + ) + tz_comps = _parse_hh_mm_ss_ff(tzstr) if all(x == 0 for x in tz_comps): @@ -1943,7 +1952,10 @@ def fromisoformat(cls, date_string): if tstr: try: - time_components, became_next_day, error_from_components = _parse_isoformat_time(tstr) + is_expanded = date_string[4] == "-" + time_components, became_next_day, error_from_components = ( + _parse_isoformat_time(tstr, is_expanded) + ) except ValueError: raise ValueError( f'Invalid isoformat string: {date_string!r}') from None diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index ecb37250ceb6c4..20973e98916783 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3390,6 +3390,7 @@ def test_fromisoformat_timespecs(self): dt_rt = self.theclass.fromisoformat(dtstr) self.assertEqual(dt, dt_rt) + @warnings_helper.ignore_warnings(category=DeprecationWarning) def test_fromisoformat_datetime_examples(self): UTC = timezone.utc BST = timezone(timedelta(hours=1), 'BST') @@ -4706,6 +4707,7 @@ def test_fromisoformat_fractions(self): self.assertEqual(actual, expected) + @warnings_helper.ignore_warnings(category=DeprecationWarning) def test_fromisoformat_time_examples(self): examples = [ ('0000', self.theclass(0, 0)), diff --git a/Misc/NEWS.d/next/Library/2025-03-20-18-00-00.gh-issue-115783.befw32.rst b/Misc/NEWS.d/next/Library/2025-03-20-18-00-00.gh-issue-115783.befw32.rst new file mode 100644 index 00000000000000..2832a9fdeffe14 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-20-18-00-00.gh-issue-115783.befw32.rst @@ -0,0 +1 @@ +Deprecate support for invalid ISO formats in :func:`datetime.datetime.fromisoformat` diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9bba0e3354b26b..e9e6a941fb08c2 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1131,6 +1131,13 @@ parse_isoformat_time(const char *dtstr, size_t dtlen, int *hour, int *minute, int tzsign = (*tzinfo_pos == '-') ? -1 : 1; tzinfo_pos++; int tzhour = 0, tzminute = 0, tzsecond = 0; + + if (tzinfo_pos + 2 < p_end && tzinfo_pos[2] != ':' && strlen(tzinfo_pos) > 2) { + PyErr_WarnEx(PyExc_DeprecationWarning, + "Support for partially expanded formats is deprecated in " + "accordance with ISO 8601:2 and will be removed in 3.15", 3); + } + rv = parse_hh_mm_ss_ff(tzinfo_pos, p_end, &tzhour, &tzminute, &tzsecond, tzmicrosecond);