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

Reply via email to