python/cpython · error · ValueError
stray %% in format '%s'
Error message
stray %% in format '%s'
What it means
When _strptime compiles the format string, an unknown directive raises a KeyError from TimeRE.__getitem__ whose key encodes the bad directive. If stripping the '\s' escape from that key leaves nothing, the format contained a stray '%' followed by a space (e.g. '% '), and this ValueError is raised.
Source
Thrown at Lib/_strptime.py:567
time.tzname != locale_time.tzname or
time.daylight != locale_time.daylight):
_TimeRE_cache = TimeRE()
_regex_cache.clear()
locale_time = _TimeRE_cache.locale_time
if len(_regex_cache) > _CACHE_MAX_SIZE:
_regex_cache.clear()
format_regex = _regex_cache.get(format)
if not format_regex:
try:
format_regex = _TimeRE_cache.compile(format)
# KeyError raised when a bad format is found; can be specified as
# \\, in which case it was a stray % but with a space after it
except KeyError as err:
bad_directive = err.args[0]
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
_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))
if len(data_string) != found.end():
rest = data_string[found.end():]
# Specific check for '%:z' directive
if (
"colon_z" in found.re.groupindex
and found.group("colon_z") is not None
and rest[0] != ":"
):
raise ValueError(
f"Missing colon in %:z before '{rest}', got '{data_string}'"View on GitHub (pinned to bc6749cc3b)
Solutions
- Escape literal percent signs in the format as '%%'
- Remove the stray '%' or the trailing space from the format string
Example fix
# before
>>> time.strptime('5 10%', '%d %')
ValueError: stray % in format '%d %'
# after
>>> time.strptime('5 10%', '%d 10%%')
time.struct_time(...) Defensive patterns
Strategy: validation
Validate before calling
import re
def sanitize_fmt(fmt: str) -> str:
# escape lone '%' not followed by a known directive
return re.sub(r'%(?![%aAbBcCxXdeFGHIjmMpSUuVwWxzZ])', '%%', fmt) Prevention
- Escape literal percent signs as %% in every format string
- Never build format strings by interpolating user data containing '%'
- Test each format string once at import time so bad formats fail fast
When it happens
Trigger: A format string containing '% ' (percent followed by whitespace), such as time.strptime(s, '%d % ') — the regex substitution %[-_0^#]*[0-9]*([OE]?[:\]?.?) consumes '%' then treats the space as the directive, producing an empty bad-directive key.
Common situations: User-built or f-string-assembled format strings where a literal '%' was not escaped as '%%'; templates that interpolate a percent into the format.
Related errors
- Day of month directive '%d' may not be used without a year d
- '%s' is a bad directive in format '%s'
- Missing colon in %:z before '{rest}', got '{data_string}'
- Day of the year directive '%j' is not compatible with ISO ye
- ISO year directive '%G' must be used with the ISO week direc
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/19e37ba716f659e3.
Report an issue: GitHub.