diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 59ac96745aa15e..3311ec88871469 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -564,15 +564,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): del err bad_directive = bad_directive.replace('\\s', '') if not bad_directive: - raise ValueError("stray %% in format '%s'" % format) from None + raise ValueError(f"stray % in format {format!r}") from None bad_directive = bad_directive.replace('\\', '', 1) - raise ValueError("'%s' is a bad directive in format '%s'" % - (bad_directive, format)) from None + raise ValueError(f"{bad_directive!r} is a bad directive " + f"in format {format!r}") from None _regex_cache[format] = format_regex found = format_regex.match(data_string) if not found: - raise ValueError("time data %r does not match format %r" % - (data_string, format)) + raise ValueError(f"time data {data_string!r} does not match " + f"format {format!r}") if len(data_string) != found.end(): rest = data_string[found.end():] # Specific check for '%:z' directive @@ -582,9 +582,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): and rest[0] != ":" ): raise ValueError( - f"Missing colon in %:z before '{rest}', got '{data_string}'" + f"Missing colon in %:z before {rest!r}, got {data_string!r}" ) - raise ValueError("unconverted data remains: %s" % rest) + raise ValueError(f"unconverted data remains: {rest!r}") iso_year = year = None month = day = 1 @@ -700,7 +700,7 @@ def parse_int(s): z = z[:3] + z[4:] if len(z) > 5: if z[5] != ':': - msg = f"Inconsistent use of : in {found_dict[group_key]}" + msg = f"Inconsistent use of : in {found_dict[group_key]!r}" raise ValueError(msg) z = z[:5] + z[6:] hours = int(z[1:3]) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index e95cc6db170e24..d70837ea63f547 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -236,7 +236,8 @@ def test_ValueError(self): directive = bad_format[1:].rstrip() with (self.subTest(format=bad_format), self.assertRaisesRegex(ValueError, - f"'{re.escape(directive)}' is a bad directive in format ")): + f"{re.escape(repr(directive))} is a bad directive " + f"in format ")): _strptime._strptime_time("2005", bad_format) msg_week_no_year_or_weekday = r"ISO week directive '%V' must be used with " \ @@ -303,6 +304,11 @@ def test_unconverteddata(self): # Check ValueError is raised when there is unconverted data self.assertRaises(ValueError, _strptime._strptime_time, "10 12", "%m") + # gh-141540: a trailing newline must be visible in the message + with self.assertRaisesRegex(ValueError, + r"unconverted data remains: '\\n'"): + _strptime._strptime_time("2001-02-03\n", "%Y-%m-%d") + def roundtrip(self, fmt, position, time_tuple=None): """Helper fxn in testing.""" if time_tuple is None: @@ -451,7 +457,8 @@ def test_bad_offset(self): with self.assertRaises(ValueError) as err: _strptime._strptime("-01:3030", "%z") - self.assertEqual("Inconsistent use of : in -01:3030", str(err.exception)) + self.assertEqual("Inconsistent use of : in '-01:3030'", + str(err.exception)) with self.assertRaises(ValueError) as err: _strptime._strptime("-01:3030", "%:z") self.assertEqual("Missing colon in %:z before '30', got '-01:3030'", diff --git a/Misc/NEWS.d/next/Library/2026-09-19-20-31-58.gh-issue-141540.phc605.rst b/Misc/NEWS.d/next/Library/2026-09-19-20-31-58.gh-issue-141540.phc605.rst new file mode 100644 index 00000000000000..1b38ffd26ea20c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-19-20-31-58.gh-issue-141540.phc605.rst @@ -0,0 +1,3 @@ +Quote the unconverted data and the format in the error messages of +:func:`time.strptime` and :meth:`datetime.datetime.strptime` so that +whitespace such as a trailing newline is visible.