{"record":{"id":"19e37ba716f659e3","repo":"python/cpython","slug":"stray-in-format-s","errorCode":null,"errorMessage":"stray %% in format '%s'","messagePattern":"stray %% in format '(.+?)'","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_strptime.py","lineNumber":567,"sourceCode":"            time.tzname != locale_time.tzname or\n            time.daylight != locale_time.daylight):\n            _TimeRE_cache = TimeRE()\n            _regex_cache.clear()\n            locale_time = _TimeRE_cache.locale_time\n        if len(_regex_cache) > _CACHE_MAX_SIZE:\n            _regex_cache.clear()\n        format_regex = _regex_cache.get(format)\n        if not format_regex:\n            try:\n                format_regex = _TimeRE_cache.compile(format)\n            # KeyError raised when a bad format is found; can be specified as\n            # \\\\, in which case it was a stray % but with a space after it\n            except KeyError as err:\n                bad_directive = err.args[0]\n                del err\n                bad_directive = bad_directive.replace('\\\\s', '')\n                if not bad_directive:\n                    raise ValueError(\"stray %% in format '%s'\" % format) from None\n                bad_directive = bad_directive.replace('\\\\', '', 1)\n                raise ValueError(\"'%s' is a bad directive in format '%s'\" %\n                                    (bad_directive, format)) from None\n            _regex_cache[format] = format_regex\n    found = format_regex.match(data_string)\n    if not found:\n        raise ValueError(\"time data %r does not match format %r\" %\n                         (data_string, format))\n    if len(data_string) != found.end():\n        rest = data_string[found.end():]\n        # Specific check for '%:z' directive\n        if (\n            \"colon_z\" in found.re.groupindex\n            and found.group(\"colon_z\") is not None\n            and rest[0] != \":\"\n        ):\n            raise ValueError(\n                f\"Missing colon in %:z before '{rest}', got '{data_string}'\"","sourceCodeStart":549,"sourceCodeEnd":585,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_strptime.py#L549-L585","documentation":"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.","triggerScenarios":"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.","commonSituations":"User-built or f-string-assembled format strings where a literal '%' was not escaped as '%%'; templates that interpolate a percent into the format.","solutions":["Escape literal percent signs in the format as '%%'","Remove the stray '%' or the trailing space from the format string"],"exampleFix":"# before\n>>> time.strptime('5 10%', '%d %')\nValueError: stray % in format '%d %'\n\n# after\n>>> time.strptime('5 10%', '%d 10%%')\ntime.struct_time(...)","handlingStrategy":"validation","validationCode":"import re\n\ndef sanitize_fmt(fmt: str) -> str:\n    # escape lone '%' not followed by a known directive\n    return re.sub(r'%(?![%aAbBcCxXdeFGHIjmMpSUuVwWxzZ])', '%%', fmt)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["strptime","format-string","escaping","cpython"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}