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
- Use YYYY-MM-DD for whole-day precision, or YYYY-MM-DDTHH:MM:SSZ for a moment in time
- Convert local times to UTC before passing: dt.astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
- Expand partial dates to a full day (first/last day of the month) at the caller, since the KG deliberately rejects partial precision
- 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
- Store dates as YYYY-MM-DD and moments as ...T00:00:00Z — nothing partial
- Always convert local times to UTC before writing
- Validate with datetime.strptime at the caller for clearer errors than the library's
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
- {field_name} must be a string
- valid_to={valid_to!r} is before valid_from={valid_from!r}; a
- valid_to={ended!r} is before valid_from={valid_from!r}; an i
- at={boundary!r} is before valid_from={valid_from!r}; an inve
- pass inline text or a file, not both
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/a2a28ab22c35709f.
Report an issue: GitHub.