RustPython/RustPython · error · ValueError
Invalid isoformat string: {time_string!r}
Error message
Invalid isoformat string: {time_string!r} What it means
time.fromisoformat() could not parse the string: after stripping an optional leading 'T', _parse_isoformat_time failed to consume it as HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]. Any leftover character, missing or non-padded component, out-of-range value, or wrong separator ends here, and the offending string is embedded in the message.
Source
Thrown at Lib/_pydatetime.py:1627
return s
__str__ = isoformat
@classmethod
def fromisoformat(cls, time_string):
"""Construct a time from a string in one of the ISO 8601 formats."""
if not isinstance(time_string, str):
raise TypeError('fromisoformat: argument must be str')
# The spec actually requires that time-only ISO 8601 strings start with
# T, but the extended format allows this to be omitted as long as there
# is no ambiguity with date strings.
time_string = time_string.removeprefix('T')
try:
return cls(*_parse_isoformat_time(time_string)[0])
except Exception:
raise ValueError(f'Invalid isoformat string: {time_string!r}')
def strftime(self, format):
"""Format using strftime(). The date part of the timestamp passed
to underlying strftime should not be used.
"""
# The year must be >= 1000 else Python's strftime implementation
# can raise a bogus exception.
timetuple = (1900, 1, 1,
self._hour, self._minute, self._second,
0, 1, -1)
return _wrap_strftime(self, format, timetuple)
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)View on GitHub (pinned to aaeab4f754)
Solutions
- Validate or normalize shape before parsing: strip whitespace, zero-pad components.
- Use time.strptime(s, '%H:%M:%S') matching the format you actually receive when it is not ISO.
- Wrap parsing per field and report which value failed instead of letting the error bubble.
- Enforce an ISO-shape regex at the API boundary.
Example fix
// before
t = time.fromisoformat(raw) # raw == '9:05' -> ValueError
// after
import re
raw = raw.strip()
if not re.fullmatch(r'\d{2}:\d{2}(:\d{2}(\.\d+)?)?', raw):
raise ValueError(f'expected HH:MM[:SS[.f]], got {raw!r}')
t = time.fromisoformat(raw) Defensive patterns
Strategy: try-catch
Validate before calling
import re
from datetime import time
ISO_TIME = re.compile(r'\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?([+-]\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?|Z)?')
def looks_like_iso_time(s: str) -> bool:
return ISO_TIME.fullmatch(s.strip()) is not None Try / catch
from datetime import time
def parse_time_field(s: str):
try:
return time.fromisoformat(s.strip())
except ValueError:
raise ValueError(f'bad time field {s!r}: expected HH:MM[:SS[.ffffff]]') from None Prevention
- Zero-pad and strip time strings before fromisoformat.
- Use strptime with an explicit format for non-ISO inputs.
- Catch ValueError per field in ETL and keep the raw string in the error report.
- Reject locale-style times ('1.30pm') at the API boundary.
When it happens
Trigger: time.fromisoformat('9:00') (single-digit hour); '12-30' (dash separator); '25:00' (hour out of range); '12:00:00+99:00' (bad offset); '12:00:00 ' (trailing space); fractional-second formats the running Python version does not accept.
Common situations: User-supplied times without strict validation; logs or exports with locale-formatted times ('1.30pm'); spreadsheets and CSVs with padded or partial strings; inputs that look ISO-ish but use different separators.
Related errors
- Invalid ISO string
- Inconsistent use of dash separator
- Incomplete time component
- Invalid time separator: %c
- Invalid microsecond separator
AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17).
Data as JSON: /api/errors/3a56b92249d83d54.
Report an issue: GitHub.