]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-131434: Improve error reporting for incorrect format in strptime() (GH-131568)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 9 Apr 2025 10:26:50 +0000 (13:26 +0300)
committerGitHub <noreply@github.com>
Wed, 9 Apr 2025 10:26:50 +0000 (13:26 +0300)
In particularly, fix regression in detecting stray % at the end of the
format string.

Lib/_strptime.py
Lib/test/test_strptime.py
Lib/test/test_time.py
Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst [new file with mode: 0644]

index e6e23596db6f998e0f231818bad0ada34602b8b1..aa63933a49d16dfb98e74d9ee0b8233852da6584 100644 (file)
@@ -365,7 +365,7 @@ class TimeRE(dict):
                     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:
index fbc43829e22a960cd4c95a04d1ab6a4caf174d4f..268230f6da78f8a299b479eae78e38879cae0dcd 100644 (file)
@@ -220,16 +220,16 @@ class StrptimeTests(unittest.TestCase):
         # 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 @@ class StrptimeTests(unittest.TestCase):
         # 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
index cd6c08f183ff9596a85ff69c98623537ef542a4a..d06f65270efe79f416704262893e92e4da72497e 100644 (file)
@@ -345,11 +345,11 @@ class TimeTestCase(unittest.TestCase):
         # 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 (file)
index 0000000..a7b0861
--- /dev/null
@@ -0,0 +1 @@
+Improve error reporting for incorrect format in :func:`time.strptime`.