{"record":{"id":"71e71bfa67d83de6","repo":"MemPalace/mempalace","slug":"since-since-r-must-be-earlier-than-before-be","errorCode":null,"errorMessage":"since ({since!r}) must be earlier than before ({before!r})","messagePattern":"since \\((.+?)\\) must be earlier than before \\((.+?)\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/date_window.py","lineNumber":81,"sourceCode":"            f\"{field_name} must be an ISO date string \"\n            f\"(e.g. '2026-04-01' or '2026-04-01T09:30:00'), got {value!r}\"\n        ) from exc\n    if parsed.tzinfo is not None:\n        parsed = parsed.replace(tzinfo=None)\n    return parsed\n\n\ndef parse_window(since: Optional[str] = None, before: Optional[str] = None):\n    \"\"\"Parse a ``[since, before)`` pair, rejecting an inverted window.\n\n    Returns ``(since_dt, before_dt)`` — either side ``None`` when absent.\n    Raises ``ValueError`` (naming the offending field or the inversion) so\n    callers surface the same message everywhere a window is accepted.\n    \"\"\"\n    since_dt = parse_date_bound(since, \"since\")\n    before_dt = parse_date_bound(before, \"before\")\n    if since_dt is not None and before_dt is not None and since_dt >= before_dt:\n        raise ValueError(f\"since ({since!r}) must be earlier than before ({before!r})\")\n    return since_dt, before_dt\n\n\ndef filed_at_in_window(\n    filed_at, since_dt: Optional[datetime], before_dt: Optional[datetime]\n) -> bool:\n    \"\"\"True if a drawer's ``filed_at`` falls in ``[since, before)``.\n\n    ``since`` is inclusive and ``before`` is exclusive, matching the issue spec.\n    Parsing (``Z``/offset normalization, tz drop) is delegated to\n    ``parse_date_bound`` so a bound and a ``filed_at`` are compared\n    identically. A drawer whose ``filed_at`` is missing or unparseable cannot\n    be confirmed in-window, so it is EXCLUDED whenever a bound is active — a\n    date-filtered listing must never silently include rows of unknown age.\n    \"\"\"\n    try:\n        filed_dt = parse_date_bound(filed_at, \"filed_at\")\n    except ValueError:","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/date_window.py#L63-L99","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["Swap the arguments so since is the earlier bound.","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).","Sort/validate bounds at the call site: if start > end, swap or surface a form error to the user."],"exampleFix":"# before (single day, zero-width window)\nparse_window(since=\"2026-04-01\", before=\"2026-04-01\")\n\n# after\nparse_window(since=\"2026-04-01\", before=\"2026-04-02\")","handlingStrategy":"validation","validationCode":"from mempalace.date_window import parse_date_bound\n\ndef ordered_window(since, before):\n    s, b = parse_date_bound(since, \"since\"), parse_date_bound(before, \"before\")\n    if s is not None and b is not None and s >= b:\n        return None  # or raise before hitting parse_window\n    return s, b","typeGuard":"def is_valid_window(since: object, before: object) -> bool:\n    try:\n        parse_window(since, before)  # noqa: returns without raising\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    since_dt, before_dt = parse_window(since, before)\nexcept ValueError as exc:\n    if \"must be earlier\" in str(exc):\n        since, before = before, since  # or return a form error\n        since_dt, before_dt = parse_window(since, before)\n    else:\n        raise","preventionTips":["Remember the window is half-open [since, before): single-day filters need before = day + 1.","Sort date-picker output (min, max) before building the window.","Validate window order at the UI/CLI boundary with a clear message."],"tags":["validation","dates","window","api-contract"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}