8000 gh-131434: trailing % in strptime format strings should raise an error · duaneg/cpython@8c386e0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8c386e0

Browse files
committed
pythongh-131434: trailing % in strptime format strings should raise an error
Treat trailing % at the end of a format string consistently with % followed by spaces inside a format string. The IndexError which was previously raised when there was a trailing % can no longer happen, so we can remove the dead code dealing with it. Note the ValueError's message in this case has changed from "stray % in format '...'" to "'%' is a bad directive in format '...'". We could treat this as a special case and retain the previous wording if desired, but ISTM that consistency is better than strict backwards compatibility here.
1 parent e356468 commit 8c386e0

File tree

2 files changed

+25
-8
lines changed

2 files changed

+25
-8
lines changed

Lib/_strptime.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def repl(m):
365365
nonlocal day_of_month_in_format
366366
day_of_month_in_format = True
367367
return self[format_char]
368-
format = re_sub(r'%(O?.)', repl, format)
368+
format = re_sub(r'%(O?.)?', repl, format)
369369
if day_of_month_in_format and not year_in_format:
370370
import warnings
371371
warnings.warn("""\
@@ -436,17 +436,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
436436
try:
437437
format_regex = _TimeRE_cache.compile(format)
438438
# KeyError raised when a bad format is found; can be specified as
439-
# \\, in which case it was a stray % but with a space after it
439+
# \\, in which case it was a stray % but with a space after it, or
440+
# None, in which case it was a trailing %
440441
except KeyError as err:
441442
bad_directive = err.args[0]
442-
if bad_directive == "\\":
443+
if bad_directive is None or bad_directive == "\\":
443444
bad_directive = "%"
444445
del err
445446
raise ValueError("'%s' is a bad directive in format '%s'" %
446447
(bad_directive, format)) from None
447-
# IndexError only occurs when the format string is "%"
448-
except IndexError:
449-
raise ValueError("stray %% in format '%s'" % format) from None
450448
_regex_cache[format] = format_regex
451449
found = format_regex.match(data_string)
452450
if not found:

Lib/test/test_strptime.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,11 @@ def test_strptime_exception_context(self):
286286
with self.assertRaises(ValueError) as e:
287287
_strptime._strptime_time('', '%D')
288288
self.assertIs(e.exception.__suppress_context__, True)
289-
# additional check for IndexError branch (issue #19545)
289+
290+
# additional check for trailing % (issue #19545)
290291
with self.assertRaises(ValueError) as e:
291292
_strptime._strptime_time('19', '%Y %')
292-
self.assertIsNone(e.exception.__context__)
293+
self.assertIs(e.exception.__suppress_context__, True)
293294

294295
def test_unconverteddata(self):
295296
# Check ValueError is raised when there is unconverted data
@@ -592,6 +593,24 @@ def test_percent(self):
592593
strp_output[1] == self.time_tuple[1],
593594
"handling of percent sign failed")
594595

596+
def test_lone_percent(self):
597+
# Trailing escaped %s should work
598+
strptime = _strptime._strptime
599+
self.assertEqual(strptime('2000 %', '%Y %%')[0][0], 2000)
600+
self.assertEqual(strptime('2000 %%', '%Y %%%%')[0][0], 2000)
601+
602+
def test(tstr, format):
603+
with self.assertRaises(ValueError) as err:
604+
strptime(tstr, format)
605+
self.assertEqual("'%' is a bad directive in format '{}'".format(format),
606+
str(err.exception))
607+
608+
# A lone or unescaped trailing % should not
609+
test("%", "%")
610+
test("2000 %", "%Y %")
611+
test("2000 % %", "%Y % %%")
612+
test("2000 %%", "%Y %%%")
613+
595614
def test_caseinsensitive(self):
596615
# Should handle names case-insensitively.
597616
strf_output = time.strftime("%B", self.time_tuple)

0 commit comments

Comments
 (0)
0