https://github.com/python/cpython/commit/3feac7a093b3fcd549c5dc54277f26f585f2ab0c
commit: 3feac7a093b3fcd549c5dc54277f26f585f2ab0c
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]