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

  1. Validate or normalize shape before parsing: strip whitespace, zero-pad components.
  2. Use time.strptime(s, '%H:%M:%S') matching the format you actually receive when it is not ISO.
  3. Wrap parsing per field and report which value failed instead of letting the error bubble.
  4. 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

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


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/3a56b92249d83d54. Report an issue: GitHub.