python/cpython · error · ValueError

Invalid isoformat string

Error message

Invalid isoformat string

What it means

_parse_isoformat_date accepts only date strings of length 7 (YYYY-Www), 8 (YYYYMMDD or YYYY-Www compact variants) or 10 (YYYY-MM-DD). Any other length raises ValueError('Invalid isoformat string') before any digits are inspected, so the content cannot matter yet.

Source

Thrown at Lib/_pydatetime.py:362

            if idx < 9:
                return idx

            if idx % 2 == 0:
                # If the index of the last number is even, it's YYYYWwwd
                return 7
            else:
                return 8
        else:
            # YYYYMMDD (8)
            return 8


def _parse_isoformat_date(dtstr):
    # It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
    # see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
    if len(dtstr) not in (7, 8, 10):
        raise ValueError("Invalid isoformat string")
    year = int(dtstr[0:4])
    has_sep = dtstr[4] == '-'

    pos = 4 + has_sep
    if dtstr[pos:pos + 1] == "W":
        # YYYY-?Www-?D?
        pos += 1
        weekno = int(dtstr[pos:pos + 2])
        pos += 2

        dayno = 1
        if len(dtstr) > pos:
            if (dtstr[pos:pos + 1] == '-') != has_sep:
                raise ValueError("Inconsistent use of dash separator")

            pos += has_sep

            dayno = int(dtstr[pos:pos + 1])

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Normalize input to YYYY-MM-DD (or the exact inverse of date.isoformat()) before parsing
  2. Zero-pad components when constructing strings: f'{y:04d}-{m:02d}-{d:02d}'
  3. For arbitrary ISO-8601 input use dateutil.parser.isoparse instead of fromisoformat

Example fix

// before
d = date.fromisoformat(user_input)  # '2021-1-1' -> ValueError

# after
from datetime import datetime
d = datetime.strptime(user_input, '%Y-%m-%d').date()
Defensive patterns

Strategy: validation

Validate before calling

import re
_DATE_RE = re.compile(r'^\d{4}-\d{2}-\d{2}$|^\d{8}$')
def parse_date(s: str):
    if not _DATE_RE.fullmatch(s):
        raise ValueError(f'expected YYYY-MM-DD or YYYYMMDD, got {s!r}')
    return date.fromisoformat(s)

Type guard

def looks_like_iso_date(s: str) -> bool:
    return len(s) in (8, 10) and s[:4].isdigit() and s.endswith(tuple('0123456789'))

Try / catch

try:
    d = date.fromisoformat(s)
except ValueError:
    d = datetime.strptime(s, '%Y-%m-%d').date()  # stricter, better message

Prevention

When it happens

Trigger: datetime.fromisoformat('2021-1-1') (length 8 but invalid shape reaches other errors; here e.g. '202101011' length 9, '2021' length 4, '2021-0101' etc.); passing a full datetime string to date.fromisoformat on older Pythons; passing a time-only string like '12:30'.

Common situations: Assuming fromisoformat is a general ISO-8601 parser (it is strict and, pre-3.11, only accepts datetime.isoformat() output); lenient user input with missing zero padding; mixing date.fromisoformat and datetime.fromisoformat on the wrong value.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/fe165c888dee46e4. Report an issue: GitHub.