python/cpython · error · ValueError

Unknown timespec value

Error message

Unknown timespec value

What it means

datetime.isoformat(timespec=...) only accepts 'auto', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'. The value is used as a key into a format-spec dict; an unknown key raises the bare ValueError('Unknown timespec value') with no echo of the bad input (historically fixed in later CPython versions to include it).

Source

Thrown at Lib/_pydatetime.py:183

def _format_time(hh, mm, ss, us, timespec='auto'):
    specs = {
        'hours': '{:02d}',
        'minutes': '{:02d}:{:02d}',
        'seconds': '{:02d}:{:02d}:{:02d}',
        'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
        'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
    }

    if timespec == 'auto':
        # Skip trailing microseconds when us==0.
        timespec = 'microseconds' if us else 'seconds'
    elif timespec == 'milliseconds':
        us //= 1000
    try:
        fmt = specs[timespec]
    except KeyError:
        raise ValueError('Unknown timespec value')
    else:
        return fmt.format(hh, mm, ss, us)

def _format_offset(off, sep=':'):
    s = ''
    if off is not None:
        if off.days < 0:
            sign = "-"
            off = -off
        else:
            sign = "+"
        hh, mm = divmod(off, timedelta(hours=1))
        mm, ss = divmod(mm, timedelta(minutes=1))
        s += "%s%02d%s%02d" % (sign, hh, sep, mm)
        if ss or ss.microseconds:
            s += "%s%02d" % (sep, ss.seconds)

            if ss.microseconds:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use one of the six exact strings: 'auto', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'
  2. Validate/normalize external input against that set before calling isoformat
  3. If the value may be absent, pass timespec only when set: dt.isoformat(**({'timespec': ts} if ts else {}))

Example fix

// before
ts = cfg.get('precision', 'ms')
s = dt.isoformat(timespec=ts)  # ValueError

# after
_VALID = {'auto','hours','minutes','seconds','milliseconds','microseconds'}
ts = cfg.get('precision', 'auto')
s = dt.isoformat(timespec=ts if ts in _VALID else 'auto')
Defensive patterns

Strategy: validation

Validate before calling

_TIMESPECS = {'auto','hours','minutes','seconds','milliseconds','microseconds'}
def safe_isoformat(dt, timespec='auto'):
    if timespec not in _TIMESPECS:
        raise ValueError(f'timespec must be one of {sorted(_TIMESPECS)}, got {timespec!r}')
    return dt.isoformat(timespec=timespec)

Type guard

def is_valid_timespec(ts: object) -> bool:
    return ts in {'auto','hours','minutes','seconds','milliseconds','microseconds'}

Prevention

When it happens

Trigger: dt.isoformat(timespec='ms') (the timedelta-style abbreviation is invalid here); timespec='secs'; a value read from a config file or env var with a typo; passing None instead of omitting the argument.

Common situations: Confusing datetime.isoformat timespec vocabulary with dateutil/ISO-8601 'millis' strings; user-supplied precision settings ('milli', 'ms', 'M'); config keys propagated from a serialization library that normalizes timespec differently across versions.

Related errors


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