]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-131434: Improve error reporting for incorrect format in strptime() (GH...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 9 Apr 2025 10:50:24 +0000 (12:50 +0200)
committerGitHub <noreply@github.com>
Wed, 9 Apr 2025 10:50:24 +0000 (10:50 +0000)
In particularly, fix regression in detecting stray % at the end of the
format string.
(cherry picked from commit 3feac7a093b3fcd549c5dc54277f26f585f2ab0c)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
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 8d763e5bd331e55daf4f8baed000782337471a7b..bf92a0b028c9abc434700b617458bdecbfc674da 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 9f5cfca9c7f1249ddf4706c04e05b48618f25001..1a42de611ab0ab9024eeb71d5e802ff1f0d33402 100644 (file)
@@ -223,16 +223,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 " \
@@ -288,11 +288,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 ee17c93f54cd8821c1dbf2083ac0a3ca4e77b6ce..705948e06109e091cd05f3a616fb56f628b9ccc1 100644 (file)
@@ -339,11 +339,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`.