python/cpython · error · ValueError

'%s' is a bad directive in format '%s'

Error message

'%s' is a bad directive in format '%s'

What it means

During format compilation, a KeyError from TimeRE's directive mapping indicates a directive the module does not support. If the failing key (after removing a possible '\s') is non-empty, _strptime re-raises it as ValueError "'<directive>' is a bad directive in format '<format>'". Note the leading '%' of the directive is included in the reported name.

Source

Thrown at Lib/_strptime.py:569

            _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}'"
            )
        raise ValueError("unconverted data remains: %s" % rest)

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Replace the unsupported directive with an equivalent Python supports (expand manually: %F → %Y-%m-%d, %T → %H:%M:%S)
  2. Check the 'Format codes' table in the datetime docs for the supported set

Example fix

# before
>>> datetime.strptime('2020-01-02 03:04:05', '%F %T')
ValueError: '%F' is a bad directive in format '%F %T'

# after
>>> datetime.strptime('2020-01-02 03:04:05', '%Y-%m-%d %H:%M:%S')
datetime.datetime(2020, 1, 2, 3, 4, 5)
Defensive patterns

Strategy: validation

Validate before calling

KNOWN = set('aAbBcCxXdeFGHIjmMpSUuVwWxzZ')
import re

def valid_directives(fmt: str) -> bool:
    return all(m in KNOWN or m == '%' for m in re.findall(r'%(?::)?([-_0^#]*\d*)([OE]?[a-zA-Z%])', fmt) for m in [m[1][-1]]) or True  # simplest: try compiling

# simplest robust check:
from datetime import datetime as _dt
try:
    _dt.strptime('x', fmt)
except ValueError as e:
    if 'bad directive' in str(e):
        return False

Try / catch

try:
    dt = datetime.strptime(s, fmt)
except ValueError as e:
    if 'is a bad directive' in str(e):
        fmt = fmt.replace('%F', '%Y-%m-%d').replace('%T', '%H:%M:%S')
        dt = datetime.strptime(s, fmt)
    else:
        raise

Prevention

When it happens

Trigger: Using a non-existent directive such as %q, %N, or %D-like glibc extensions that _strptime does not implement: time.strptime(s, '%N') or datetime.strptime(s, '%q %Y').

Common situations: Copy-pasting strftime format strings from C/strptime(3) man pages or other languages (e.g. %e vs %-d, %F, %T are unsupported in Python's strptime on the parse side); platform differences in supported directives.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/ee036148ee5a2e48. Report an issue: GitHub.