https://github.com/python/cpython/commit/3feac7a093b3fcd549c5dc54277f26f585f2ab0c commit: 3feac7a093b3fcd549c5dc54277f26f585f2ab0c branch: main author: Serhiy Storchaka <storch...@gmail.com> committer: serhiy-storchaka <storch...@gmail.com> date: 2025-04-09T13:26:50+03:00 summary:
gh-131434: Improve error reporting for incorrect format in strptime() (GH-131568) In particularly, fix regression in detecting stray % at the end of the format string. files: A Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst M Lib/_strptime.py M Lib/test/test_strptime.py M Lib/test/test_time.py diff --git a/Lib/_strptime.py b/Lib/_strptime.py index e6e23596db6f99..aa63933a49d16d 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -365,7 +365,7 @@ def repl(m): nonlocal day_of_month_in_format day_of_month_in_format = True return self[format_char] - format = re_sub(r'%(O?.)', repl, format) + format = re_sub(r'%([OE]?\\?.?)', repl, format) if day_of_month_in_format and not year_in_format: import warnings warnings.warn("""\ @@ -439,14 +439,13 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # \\, in which case it was a stray % but with a space after it except KeyError as err: bad_directive = err.args[0] - if bad_directive == "\\": - bad_directive = "%" del err + bad_directive = bad_directive.replace('\\s', '') + if not bad_directive: + raise ValueError("stray %% in format '%s'" % format) from None + bad_directive = bad_directive.replace('\\', '', 1) raise ValueError("'%s' is a bad directive in format '%s'" % (bad_directive, format)) from None - # IndexError only occurs when the format string is "%" - except IndexError: - raise ValueError("stray %% in format '%s'" % format) from None _regex_cache[format] = format_regex found = format_regex.match(data_string) if not found: diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index fbc43829e22a96..268230f6da78f8 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -220,16 +220,16 @@ def test_ValueError(self): # Make sure ValueError is raised when match fails or format is bad self.assertRaises(ValueError, _strptime._strptime_time, data_string="%d", format="%A") - for bad_format in ("%", "% ", "%e"): - try: + for bad_format in ("%", "% ", "%\n"): + with self.assertRaisesRegex(ValueError, "stray % in format "): + _strptime._strptime_time("2005", bad_format) + for bad_format in ("%e", "%Oe", "%O", "%O ", "%Ee", "%E", "%E ", + "%.", "%+", "%_", "%~", "%\\", + "%O.", "%O+", "%O_", "%O~", "%O\\"): + directive = bad_format[1:].rstrip() + with self.assertRaisesRegex(ValueError, + f"'{re.escape(directive)}' is a bad directive in format "): _strptime._strptime_time("2005", bad_format) - except ValueError: - continue - except Exception as err: - self.fail("'%s' raised %s, not ValueError" % - (bad_format, err.__class__.__name__)) - else: - self.fail("'%s' did not raise ValueError" % bad_format) msg_week_no_year_or_weekday = r"ISO week directive '%V' must be used with " \ r"the ISO year directive '%G' and a weekday directive " \ @@ -285,11 +285,11 @@ def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: _strptime._strptime_time('', '%D') - self.assertIs(e.exception.__suppress_context__, True) - # additional check for IndexError branch (issue #19545) + self.assertTrue(e.exception.__suppress_context__) + # additional check for stray % branch with self.assertRaises(ValueError) as e: - _strptime._strptime_time('19', '%Y %') - self.assertIsNone(e.exception.__context__) + _strptime._strptime_time('%', '%') + self.assertTrue(e.exception.__suppress_context__) def test_unconverteddata(self): # Check ValueError is raised when there is unconverted data diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index cd6c08f183ff95..d06f65270efe79 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -345,11 +345,11 @@ def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) with self.assertRaises(ValueError) as e: time.strptime('', '%D') - self.assertIs(e.exception.__suppress_context__, True) - # additional check for IndexError branch (issue #19545) + self.assertTrue(e.exception.__suppress_context__) + # additional check for stray % branch with self.assertRaises(ValueError) as e: - time.strptime('19', '%Y %') - self.assertIsNone(e.exception.__context__) + time.strptime('%', '%') + self.assertTrue(e.exception.__suppress_context__) def test_strptime_leap_year(self): # GH-70647: warns if parsing a format with a day and no year. diff --git a/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst b/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst new file mode 100644 index 00000000000000..a7b086131cf448 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst @@ -0,0 +1 @@ +Improve error reporting for incorrect format in :func:`time.strptime`. _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com