truth-engine
Deterministic RRULE expansion with DST handling for AI calendar agents.
LLMs struggle with recurrence rule math — they hallucinate dates, mishandle DST transitions, and can't reliably compute "3rd Tuesday of each month." The Truth Engine replaces inference with deterministic computation using the rrule crate and chrono-tz.
Usage
use ;
// Expand a recurrence rule into concrete instances
let events = expand_rrule.unwrap;
// Each event has start/end as DateTime<Utc>
for event in &events
// Detect overlapping events between two schedules
let conflicts = find_conflicts;
for c in &conflicts
// Find free slots in a time window
let free = find_free_slots;
Features
RRULE Expansion
- Full RFC 5545 recurrence rule support via the
rrulecrate v0.14 FREQ: DAILY, WEEKLY, MONTHLY, YEARLYBYDAY,BYMONTH,BYMONTHDAY,BYSETPOS,INTERVAL,COUNT,UNTIL- EXDATE exclusions via
expand_rrule_with_exdates() - DST-aware: events at 14:00 Pacific stay at 14:00 Pacific across DST transitions (UTC offset shifts automatically)
- Leap year handling:
BYMONTHDAY=29in February correctly skips non-leap years
Conflict Detection
- Pairwise overlap detection between two event lists
- Overlap defined as
a.start < b.end && b.start < a.end - Adjacent events (end == start) are NOT conflicts
- Returns overlap duration in minutes
Free/Busy Computation
- Merges overlapping busy periods
- Computes free gaps within a time window
find_first_free_slot()for minimum-duration search
API
expand_rrule(rrule, dtstart, duration_minutes, timezone, until, count)
Expands an RRULE string into concrete ExpandedEvent instances.
expand_rrule_with_exdates(rrule, dtstart, duration_minutes, timezone, until, count, exdates)
Same as above but excludes specific dates (RFC 5545 EXDATE).
find_conflicts(events_a, events_b) -> Vec<Conflict>
Finds all pairwise overlaps between two event lists.
find_free_slots(events, window_start, window_end) -> Vec<FreeSlot>
Computes free time slots within a window, merging overlapping busy periods.
find_first_free_slot(events, window_start, window_end, min_duration_minutes) -> Option<FreeSlot>
Finds the earliest free slot of at least the given duration.
Architecture
expander.rs ← RRULE string → Vec<ExpandedEvent> (wraps rrule + chrono-tz)
conflict.rs ← Two event lists → Vec<Conflict> (pairwise overlap detection)
freebusy.rs ← Events + window → Vec<FreeSlot> (gap computation)
dst.rs ← DstPolicy enum (Skip, ShiftForward, WallClock)
error.rs ← TruthError enum (InvalidRule, InvalidTimezone, Expansion)
Testing
33 tests across four suites:
- 11 expander tests — CTO's monthly 3rd Tuesday example, DST transitions, daily/weekly/biweekly, COUNT, UNTIL, duration
- 7 conflict tests — overlapping, non-overlapping, adjacent, contained, multiple, empty
- 7 free/busy tests — single event, merged overlapping, empty, min-duration, fully booked
- 8 RFC 5545 compliance vectors — biweekly multi-day, yearly, leap year Feb 29, EXDATE, COUNT, INTERVAL, BYSETPOS last weekday, multi-rule intersection
License
MIT OR Apache-2.0