{"record":{"id":"4b7f176cfa102a9f","repo":"python/cpython","slug":"non-digit-values-in-fraction","errorCode":null,"errorMessage":"Non-digit values in fraction","messagePattern":"Non-digit values in fraction","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":430,"sourceCode":"\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\ndef _parse_isoformat_time(tstr):\n    # Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]\n    len_str = len(tstr)\n    if len_str < 2:","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L412-L448","documentation":"Once a '.' or ',' fraction separator is seen, every remaining character must be an ASCII digit. Any letter, sign, or whitespace in the fraction (e.g. '12:30:00.5Z0', '12:30:00.+01:00') raises ValueError('Non-digit values in fraction'). Note this also fires when a timezone suffix was not split off correctly, because _parse_hh_mm_ss_ff receives the naive time portion only.","triggerScenarios":"datetime.fromisoformat('2021-01-01T12:30:00.000+01:00') works, but a malformed sign placement like '12:30:00.5+1:00' can reach the digit check on some paths; fraction fields containing spaces or symbols from bad exports; tz_pos detection missing a non-standard suffix character (e.g. lowercase 'z' in older versions).","commonSituations":"Lowercase 'z' UTC markers on Python < 3.11 fromisoformat; mixed precision where fraction and offset run together after string surgery; fixed-width record parsing whose offsets drifted.","solutions":["Uppercase the UTC marker and validate shape: s = s.replace('z', 'Z') then re-check before parsing (on Pythons that lack full ISO support)","Upgrade to Python 3.11+, where fromisoformat handles most valid ISO-8601 including 'Z'","Use dateutil.parser.isoparse for permissive but standards-correct ISO input"],"exampleFix":"// before\ndt = datetime.fromisoformat('2021-01-01T12:30:00.000z')  # may hit fraction/garbage errors on <3.11\n\n# after\ndt = datetime.fromisoformat('2021-01-01T12:30:00.000Z')  # 3.11+\n# or: from dateutil.parser import isoparse; dt = isoparse(raw)","handlingStrategy":"validation","validationCode":"def norm_utc_marker(s: str) -> str:\n    return s[:-1] + 'Z' if s.endswith('z') else s","typeGuard":"def fraction_is_digits(t: str) -> bool:\n    import re\n    m = re.search(r'[.,](\\d*)$', t)\n    return m is None or m.group(1).isdigit()","tryCatchPattern":"try:\n    dt = datetime.fromisoformat(s)\nexcept ValueError:\n    from dateutil.parser import isoparse\n    dt = isoparse(s)  # tolerant fallback for legacy feeds","preventionTips":["Uppercase 'z' UTC markers before parsing on older Pythons","Upgrade to Python 3.11+ for full ISO-8601 fromisoformat coverage","Validate the whole timestamp with one regex before calling fromisoformat"],"tags":["datetime","fromisoformat","fraction","valueerror","stdlib"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}