MemPalace/mempalace · error · ValueError

{field_name} must be an ISO date string

Error message

{field_name} must be an ISO date string

What it means

ValueError from parse_date_bound (mempalace/date_window.py:49): the value passed for a date filter is not a string at all (None is allowed and means no filter; blank/whitespace strings also mean no filter). The guard exists before any parsing so non-string inputs (ints, datetimes, lists) fail fast with the field's name rather than deep inside fromisoformat.

Source

Thrown at mempalace/date_window.py:49

    comparison against drawer ``filed_at`` values, which are stored as naive
    local ISO strings (``datetime.now().isoformat()``). Any timezone offset on
    the input is dropped so an aware bound never raises a ``TypeError`` against
    a naive ``filed_at``. Comparison is therefore wall-clock, which is what the
    local-first single-machine model wants; an offset bound is matched on its
    wall-clock fields, not its absolute instant, so a bound whose offset differs
    from the zone ``filed_at`` was recorded in is matched by clock time.
    The accepted grammar is a date, an ISO timestamp (optionally fractional),
    and an optional ``Z``/``±HH:MM`` offset; other ISO 8601 forms (basic format,
    week dates) are outside the contract and are rejected on the Python 3.9 floor
    even where a newer ``fromisoformat`` would accept them.
    Blank / whitespace-only means "no filter" (``None``).
    Raises ``ValueError`` on an unparseable value so the caller can surface a
    clear error, mirroring the wing/room sanitizers.
    """
    if value is None:
        return None
    if not isinstance(value, str):
        raise ValueError(f"{field_name} must be an ISO date string")
    value = value.strip()
    if not value:
        return None
    # datetime.fromisoformat before Python 3.11 rejects a trailing "Z" (Zulu),
    # and appending "+00:00" would break a date-only value on 3.9/3.10
    # ("2026-04-01+00:00" is rejected there). Any offset is dropped below for
    # wall-clock comparison anyway, so just strip a trailing Z/z; both date and
    # date-time Zulu inputs then parse on the 3.9 floor.
    iso = value[:-1] if value.endswith(("Z", "z")) else value
    try:
        parsed = datetime.fromisoformat(iso)
    except ValueError as exc:
        raise ValueError(
            f"{field_name} must be an ISO date string "
            f"(e.g. '2026-04-01' or '2026-04-01T09:30:00'), got {value!r}"
        ) from exc
    if parsed.tzinfo is not None:
        parsed = parsed.replace(tzinfo=None)

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Pass ISO 8601 strings: '2026-04-01' or '2026-04-01T09:30:00' (a trailing Z is tolerated).
  2. Convert datetime objects first: dt.date().isoformat() or dt.isoformat().
  3. Omit the parameter or pass None/'' when no filter is wanted.

Example fix

# before
results = search(since=start_dt)  # datetime object

# after
results = search(since=start_dt.date().isoformat())
Defensive patterns

Strategy: type-guard

Validate before calling

def as_iso_or_none(value):
    if value is None or (isinstance(value, str) and not value.strip()):
        return None
    if not isinstance(value, str):
        raise TypeError("date filter must be an ISO string")
    return value

Type guard

from datetime import date, datetime

def is_iso_date_str(value: object) -> bool:
    if not isinstance(value, str) or not value.strip():
        return False
    try:
        datetime.fromisoformat(value.rstrip("Zz"))
        return True
    except ValueError:
        return False

Try / catch

try:
    since_dt, before_dt = parse_window(since, before)
except ValueError as exc:
    raise UserInputError(str(exc)) from exc  # surface to the caller; no retry

Prevention

When it happens

Trigger: Passing since=20260401 (int), before=datetime.now(), or a date object instead of an ISO string to search/list APIs that accept date windows; MCP tool callers sending a JSON number instead of a string; programmatic callers reusing datetime objects from other APIs.

Common situations: MCP/JSON tool boundaries where types loosen (number-vs-string); refactoring call sites from datetimes to strings incompletely; Python callers assuming the API accepts datetime like other libraries do.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/34ff17069b5347e8. Report an issue: GitHub.