{"record":{"id":"da4f176746808b6f","repo":"python/cpython","slug":"unknown-timespec-value","errorCode":null,"errorMessage":"Unknown timespec value","messagePattern":"Unknown timespec value","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":183,"sourceCode":"\ndef _format_time(hh, mm, ss, us, timespec='auto'):\n    specs = {\n        'hours': '{:02d}',\n        'minutes': '{:02d}:{:02d}',\n        'seconds': '{:02d}:{:02d}:{:02d}',\n        'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',\n        'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'\n    }\n\n    if timespec == 'auto':\n        # Skip trailing microseconds when us==0.\n        timespec = 'microseconds' if us else 'seconds'\n    elif timespec == 'milliseconds':\n        us //= 1000\n    try:\n        fmt = specs[timespec]\n    except KeyError:\n        raise ValueError('Unknown timespec value')\n    else:\n        return fmt.format(hh, mm, ss, us)\n\ndef _format_offset(off, sep=':'):\n    s = ''\n    if off is not None:\n        if off.days < 0:\n            sign = \"-\"\n            off = -off\n        else:\n            sign = \"+\"\n        hh, mm = divmod(off, timedelta(hours=1))\n        mm, ss = divmod(mm, timedelta(minutes=1))\n        s += \"%s%02d%s%02d\" % (sign, hh, sep, mm)\n        if ss or ss.microseconds:\n            s += \"%s%02d\" % (sep, ss.seconds)\n\n            if ss.microseconds:","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L165-L201","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use one of the six exact strings: 'auto', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'","Validate/normalize external input against that set before calling isoformat","If the value may be absent, pass timespec only when set: dt.isoformat(**({'timespec': ts} if ts else {}))"],"exampleFix":"// before\nts = cfg.get('precision', 'ms')\ns = dt.isoformat(timespec=ts)  # ValueError\n\n# after\n_VALID = {'auto','hours','minutes','seconds','milliseconds','microseconds'}\nts = cfg.get('precision', 'auto')\ns = dt.isoformat(timespec=ts if ts in _VALID else 'auto')","handlingStrategy":"validation","validationCode":"_TIMESPECS = {'auto','hours','minutes','seconds','milliseconds','microseconds'}\ndef safe_isoformat(dt, timespec='auto'):\n    if timespec not in _TIMESPECS:\n        raise ValueError(f'timespec must be one of {sorted(_TIMESPECS)}, got {timespec!r}')\n    return dt.isoformat(timespec=timespec)","typeGuard":"def is_valid_timespec(ts: object) -> bool:\n    return ts in {'auto','hours','minutes','seconds','milliseconds','microseconds'}","tryCatchPattern":null,"preventionTips":["Centralize timespec handling behind one validated helper","Normalize short forms ('ms' -> 'milliseconds') at the config boundary","Unit-test every user-selectable precision value"],"tags":["datetime","isoformat","timespec","valueerror","stdlib"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}