{"record":{"id":"5349af4915844b63","repo":"python/cpython","slug":"invalid-microsecond-separator","errorCode":null,"errorMessage":"Invalid microsecond separator","messagePattern":"Invalid microsecond separator","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":426,"sourceCode":"        time_comps[comp] = int(tstr[pos:pos+2])\n\n        pos += 2\n        next_char = tstr[pos:pos+1]\n\n        if comp == 0:\n            has_sep = next_char == ':'\n\n        if not next_char or comp >= 2:\n            break\n\n        if has_sep and next_char != ':':\n            raise ValueError(\"Invalid time separator: %c\" % next_char)\n\n        pos += has_sep\n\n    if pos < len_str:\n        if tstr[pos] not in '.,':\n            raise ValueError(\"Invalid microsecond separator\")\n        else:\n            pos += 1\n            if not all(map(_is_ascii_digit, tstr[pos:])):\n                raise ValueError(\"Non-digit values in fraction\")\n\n            len_remainder = len_str - pos\n\n            if len_remainder >= 6:\n                to_parse = 6\n            else:\n                to_parse = len_remainder\n\n            time_comps[3] = int(tstr[pos:(pos+to_parse)])\n            if to_parse < 6:\n                time_comps[3] *= _FRACTION_CORRECTION[to_parse-1]\n\n    return time_comps\n","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L408-L444","documentation":"After HH[:MM[:SS]] is consumed, the only legal next character is '.' or ',' starting the fractional-second part. Anything else (e.g. '12:30:00;5', trailing garbage after seconds) raises ValueError('Invalid microsecond separator').","triggerScenarios":"datetime.fromisoformat('2021-01-01T12:30:00;500'); '12:30:00 000'; a timezone marker glued without sign such that parsing lands on a non '.,' char; trailing whitespace or junk after seconds.","commonSituations":"Excel/CSV exports using ';' or space as fraction delimiter; strings with invisible trailing characters (\\r, \\n, zero-width space) after seconds; half-sanitized timestamps where the 'T' was replaced but other chars left.","solutions":["Accept both '.' and ',' by normalizing: s = s.replace(',', '.') applied only to the fraction position, or keep ',' as fromisoformat already allows it","Strip/validate the tail: s.strip() and check re.fullmatch on the whole timestamp before parsing","Trim junk after seconds with a regex capturing HH:MM:SS[.,fff]"],"exampleFix":"// before\ndt = datetime.fromisoformat('2021-01-01T12:30:00;500')  # ValueError\n\n# after\ns = raw.replace(';', '.')\ndt = datetime.fromisoformat(s)","handlingStrategy":"validation","validationCode":"def norm_fraction_sep(t: str) -> str:\n    # allow ';' or ' ' as the fraction delimiter like ','\n    import re\n    return re.sub(r'(?<=\\d\\d)[; ](?=\\d)', '.', t)","typeGuard":"def fraction_separator_valid(t: str) -> bool:\n    i = max(t.find('.'), t.find(','))\n    return i == -1 or t[i] in '.,'","tryCatchPattern":null,"preventionTips":["fromisoformat accepts both '.' and ',' as fraction separators; map others to one of these at ingest","Strip whitespace/CR/LF from timestamps before parsing","Reject trailing junk after seconds with a fullmatch regex"],"tags":["datetime","fromisoformat","fraction","separator","valueerror","stdlib"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}