python/cpython · error · ValueError

Missing colon in %:z before '{rest}', got '{data_string}'

Error message

Missing colon in %:z before '{rest}', got '{data_string}'

What it means

Specific to the %:z (colon-separated UTC offset) directive: the regex accepts an offset like +05 without requiring the colon, so when the overall match succeeds but leaves unparsed remainder whose first character is not ':', _strptime raises this f-string ValueError pointing at the leftover text.

Source

Thrown at Lib/_strptime.py:584

                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)

    iso_year = year = None
    month = day = 1
    hour = minute = second = fraction = 0
    tz = -1
    gmtoff = None
    gmtoff_fraction = 0
    iso_week = week_of_year = None
    week_of_year_start = None
    # weekday and julian defaulted to None so as to signal need to calculate
    # values
    weekday = julian = None
    found_dict = found.groupdict()
    if locale_time.LC_alt_digits:
        def parse_int(s):

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use %z for compact ±HHMM offsets — it accepts both '+0500' and (on most platforms) '+05:00'
  2. Keep %:z only when the input genuinely uses colons: '+05:00'
  3. Prefer datetime.fromisoformat() for full ISO 8601 timestamps including offsets

Example fix

# before
>>> datetime.strptime('+0500', '%:z')
ValueError: Missing colon in %:z before '00', got '+0500'

# after
>>> datetime.strptime('+0500', '%z')
datetime.datetime(2025, 8, 14, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=5)))
Defensive patterns

Strategy: validation

Validate before calling

import re

def pick_offset_fmt(s: str) -> str:
    return '%:z' if re.search(r'[+-]\d{2}:', s) else '%z'

Try / catch

try:
    dt = datetime.strptime(s, '%:z')
except ValueError as e:
    if 'Missing colon' in str(e):
        dt = datetime.strptime(s, '%z')
    else:
        raise

Prevention

When it happens

Trigger: datetime.strptime('+0500', '%:z') or '+05' style compact offsets parsed with %:z; also '+05:00' mis-typed as '+0500:' or any %:z input missing the colon after the hours.

Common situations: Assuming %:z and %z are interchangeable; feeding ISO 8601 basic-format offsets (±HHMM) into a format written for extended format (±HH:MM).

Related errors


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