MemPalace/mempalace · error · ValueError

since ({since!r}) must be earlier than before ({before!r})

Error message

since ({since!r}) must be earlier than before ({before!r})

What it means

ValueError from parse_window (mempalace/date_window.py:81): both bounds parsed successfully, but since is not strictly earlier than before (>= comparison fails). The window is half-open [since, before), so an inverted or zero-width window (since == before) is rejected as a caller error. Both values are shown repr()'d in the message.

Source

Thrown at mempalace/date_window.py:81

            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)
    return parsed


def parse_window(since: Optional[str] = None, before: Optional[str] = None):
    """Parse a ``[since, before)`` pair, rejecting an inverted window.

    Returns ``(since_dt, before_dt)`` — either side ``None`` when absent.
    Raises ``ValueError`` (naming the offending field or the inversion) so
    callers surface the same message everywhere a window is accepted.
    """
    since_dt = parse_date_bound(since, "since")
    before_dt = parse_date_bound(before, "before")
    if since_dt is not None and before_dt is not None and since_dt >= before_dt:
        raise ValueError(f"since ({since!r}) must be earlier than before ({before!r})")
    return since_dt, before_dt


def filed_at_in_window(
    filed_at, since_dt: Optional[datetime], before_dt: Optional[datetime]
) -> bool:
    """True if a drawer's ``filed_at`` falls in ``[since, before)``.

    ``since`` is inclusive and ``before`` is exclusive, matching the issue spec.
    Parsing (``Z``/offset normalization, tz drop) is delegated to
    ``parse_date_bound`` so a bound and a ``filed_at`` are compared
    identically. A drawer whose ``filed_at`` is missing or unparseable cannot
    be confirmed in-window, so it is EXCLUDED whenever a bound is active — a
    date-filtered listing must never silently include rows of unknown age.
    """
    try:
        filed_dt = parse_date_bound(filed_at, "filed_at")
    except ValueError:

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Swap the arguments so since is the earlier bound.
  2. For a single-day window, use before = since_date + 1 day (e.g. since='2026-04-01', before='2026-04-02') since the window is [since, before).
  3. Sort/validate bounds at the call site: if start > end, swap or surface a form error to the user.

Example fix

# before (single day, zero-width window)
parse_window(since="2026-04-01", before="2026-04-01")

# after
parse_window(since="2026-04-01", before="2026-04-02")
Defensive patterns

Strategy: validation

Validate before calling

from mempalace.date_window import parse_date_bound

def ordered_window(since, before):
    s, b = parse_date_bound(since, "since"), parse_date_bound(before, "before")
    if s is not None and b is not None and s >= b:
        return None  # or raise before hitting parse_window
    return s, b

Type guard

def is_valid_window(since: object, before: object) -> bool:
    try:
        parse_window(since, before)  # noqa: returns without raising
        return True
    except ValueError:
        return False

Try / catch

try:
    since_dt, before_dt = parse_window(since, before)
except ValueError as exc:
    if "must be earlier" in str(exc):
        since, before = before, since  # or return a form error
        since_dt, before_dt = parse_window(since, before)
    else:
        raise

Prevention

When it happens

Trigger: Swapping the arguments (parse_window(before=x, since=y) or passing the later date first); an equal-instant pair like since='2026-04-01', before='2026-04-01' expecting an inclusive single-day window; timezone-stripped comparisons where offsets masked the real order (both sides are normalized to naive wall-clock before comparing).

Common situations: UI or CLI date pickers returning start/end in the wrong order; code computing before = since + delta with a negative delta; single-day filters needing [day, day+1) instead of [day, day).

Related errors


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