Skip to content

Add CalDAV time-range filtering with server-side query support - #1

Closed
thoscut wants to merge 1 commit into
mainfrom
claude/caldav-time-range-filter-0lrbbu
Closed

Add CalDAV time-range filtering with server-side query support#1
thoscut wants to merge 1 commit into
mainfrom
claude/caldav-time-range-filter-0lrbbu

Conversation

@thoscut

@thoscut thoscut commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Implements server-side time-range filtering for CalDAV event queries. The range parameter of CalDavClient::list_events was previously bound as _range and ignored — the start/end parameters of the list_events tool had no effect on any server, contradicting the docs.

list_events now converts ISO 8601 date/time strings to iCalendar UTC basic format and issues an RFC 4791 calendar-query REPORT with a time-range element to fetch only events intersecting the requested window.

Key Changes

  • New time_filter module (crates/caldav/src/time_filter.rs):
    • to_ical_utc(): Converts ISO 8601 strings (offset, naive, or date-only) to iCalendar UTC basic format (YYYYMMDDTHHMMSSZ)
    • event_overlaps() / filter_events(): Client-side overlap filtering used by the mock client so tests exercise the range semantics
  • LibDavCalDavClient::list_events (crates/caldav/src/client.rs):
    • With a TimeRange, performs a server-side calendar-query REPORT with the time-range filter nested inside comp-filter VCALENDAR > VEVENT (the only placement Nextcloud and other servers honor), then fetches only matching resources via calendar-multiget
    • build_time_range_query() helper constructs the properly-nested RFC 4791 query
    • range = None behavior unchanged (all events returned); public trait signature unchanged
  • MockCalDavClient::list_events (crates/caldav/src/tool.rs): applies the same range filtering; new test suite for boundary overlaps, all-day events, missing DTEND, and None range
  • crates/agents/src/lib.rs: re-exports the time crate so moltis-caldav uses consistent date/time types without a new direct dependency (no Cargo.toml/Cargo.lock change)
  • Docs (docs/src/caldav.md): documents that filtering runs server-side when both start and end are provided

Implementation Details

  • Naive date-times (no UTC offset) are treated as UTC; date-only values resolve to midnight UTC
  • Events without DTEND are treated as instantaneous, except all-day events which span one full day
  • Overlap detection uses inclusive boundaries: events are excluded only if they end before range.start or start after range.end
  • Missing or unparseable event times are kept rather than silently dropped
  • A unit test asserts the generated REPORT XML nests time-range under VCALENDAR > VEVENT and uses UTC Z timestamps

Validation

Completed

  • cargo test -p moltis-caldav — 48/48 passed
  • cargo test -p moltis-agents — 418/418 passed
  • cargo clippy -p moltis-caldav -p moltis-agents --all-features --all-targets — no warnings
  • cargo fmt --all -- --check — clean
  • cargo fetch --locked — lockfile in sync
  • just build-release — Linux amd64 release build succeeded (target/release/moltis, ELF x86-64)

Remaining

  • just lint — full-workspace clippy with --all-features (requires CUDA toolchain; not available in the dev container, covered by CI)
  • just test — full-workspace nextest with --all-features (same CUDA constraint)
  • E2E verification against a real CalDAV server

Manual QA

  1. Configure a CalDAV account in moltis.toml ([caldav] with enabled = true and an account with url/username/password).
  2. Call the caldav tool with operation = list_events, a calendar href, and start/end (ISO 8601, e.g. 2026-07-01T00:00:002026-07-31T23:59:59).
  3. Verify only events intersecting that window are returned; events overlapping the boundaries must be included.
  4. Repeat without start/end — all events must be returned (unchanged behavior).
  5. Optional: capture the HTTP traffic and confirm the REPORT body nests <C:time-range start="...Z" end="...Z"/> inside comp-filter name="VEVENT" within comp-filter name="VCALENDAR".

https://claude.ai/code/session_012UDpqLhTib2oMkPj2roqaB

…-query

The range parameter of CalDavClient::list_events was bound as _range and
never used: the client always fetched every resource in the calendar, so
the start/end parameters of the list_events tool had no effect on any
server, contradicting the docs.

list_events now issues a CalDAV calendar-query REPORT (RFC 4791) with a
time-range filter nested inside comp-filter VCALENDAR > VEVENT — the only
placement Nextcloud and other servers honor — and then fetches only the
matching resources via calendar-multiget. Range bounds are parsed from
ISO 8601 (offset, naive, or date-only) and serialized as UTC with a
trailing Z (e.g. 20260101T000000Z) as the spec requires; naive inputs are
treated as UTC. When range is None, behavior is unchanged.

Date/time handling uses the time crate through a new re-export from
moltis-agents (which already depends on it), so no new direct dependency
or lockfile change is needed.

The mock client applies equivalent client-side overlap filtering (new
time_filter module) so tests exercise the range semantics: events fully
outside the range are dropped, boundary-overlapping events are kept,
all-day events span a full day, and events without DTEND are treated as
instantaneous. A unit test also asserts the generated REPORT XML nests
time-range under VCALENDAR > VEVENT with UTC Z timestamps.

Claude-Session: https://claude.ai/code/session_012UDpqLhTib2oMkPj2roqaB

thoscut commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR: moltis-org#1147 (same branch, claude/caldav-time-range-filter-0lrbbu). Closing this fork-internal PR.


Generated by Claude Code

@thoscut thoscut closed this Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants