MemPalace/mempalace · error · ValueError

{field_name}={value!r} is not a valid ISO-8601 date or UTC d

Error message

{field_name}={value!r} is not a valid ISO-8601 date or UTC datetime (expected YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)

What it means

Raised by sanitize_iso_temporal() when the string does not parse as a calendar-valid ISO-8601 date or UTC datetime (checked by _validate_iso_temporal_calendar). The KG stores temporal values as TEXT and relies on one canonical shape for lexicographic comparison; only full dates (YYYY-MM-DD) or exact UTC datetimes (YYYY-MM-DDTHH:MM:SSZ, or +00:00 which is normalized to Z) are accepted. Partial dates (YYYY-MM), local-time datetimes without a Z/offset, and calendar-invalid values (2026-02-30) are all rejected because mixed formats would silently return wrong query results.

Source

Thrown at mempalace/config.py:188

    - ``YYYY-MM-DDTHH:MM:SSZ``
    - ``YYYY-MM-DDTHH:MM:SS+00:00`` normalized to ``...Z``

    Partial dates are rejected because KG queries compare TEXT temporal values.
    Non-canonical datetime forms are rejected because mixed temporal string
    formats can silently return wrong KG query results.
    """

    if value is None or value == "":
        return value
    if not isinstance(value, str):
        raise ValueError(f"{field_name} must be a string")

    value = value.strip()

    try:
        _validate_iso_temporal_calendar(value)
    except ValueError:
        raise ValueError(
            f"{field_name}={value!r} is not a valid ISO-8601 date or UTC datetime "
            "(expected YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)"
        ) from None

    if value.endswith("+00:00"):
        value = f"{value[:-6]}Z"

    return value


def sanitize_iso_date(value, field_name: str = "date"):
    """Backward-compatible wrapper for ISO temporal validation.

    Historically this accepted only full dates. It now also accepts canonical
    UTC datetimes, but the old name is kept so existing imports continue to
    work.
    """

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Use YYYY-MM-DD for whole-day precision, or YYYY-MM-DDTHH:MM:SSZ for a moment in time
  2. Convert local times to UTC before passing: dt.astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
  3. Expand partial dates to a full day (first/last day of the month) at the caller, since the KG deliberately rejects partial precision
  4. Run the value through datetime.strptime(value, '%Y-%m-%d') (or the full format) first to catch errors early with a clearer message

Example fix

# before
kg.add_fact(s, p, o, valid_from="2026-08")

# after
kg.add_fact(s, p, o, valid_from="2026-08-01")
Defensive patterns

Strategy: validation

Validate before calling

from datetime import datetime, timezone

# Canonicalize before the call:
def to_canonical(s: str) -> str:
    try:
        datetime.strptime(s, "%Y-%m-%d")
        return s
    except ValueError:
        pass
    dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
    if dt.tzinfo is None:
        raise SystemExit("attach a timezone: pass UTC '...Z' values")
    return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

Type guard

def is_canonical_temporal(value: str) -> bool:
    try:
        datetime.strptime(value, "%Y-%m-%d")
        return True
    except ValueError:
        pass
    try:
        datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
        return True
    except ValueError:
        return False

Try / catch

try:
    t = sanitize_iso_temporal(v, "valid_from")
except ValueError as exc:
    if "not a valid ISO-8601" in str(exc):
        t = sanitize_iso_temporal(to_canonical(v), "valid_from")
    else:
        raise

Prevention

When it happens

Trigger: Passing '2026-08' (partial month), '2026-08-14T10:00:00' (no timezone), '2026-13-01' (invalid month), '14/08/2026' (non-ISO order), or any string datetime.fromisoformat-style parsing would accept but that lacks UTC designation.

Common situations: Feeding user-typed dates like 'Aug 2026'; forwarding ISO strings from other APIs that omit the timezone; legacy data using local-time timestamps; date components out of range.

Related errors


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