use chrono::Datelike;
use truth_engine::{expand_rrule, ExpandedEvent};
fn dates(events: &[ExpandedEvent]) -> Vec<(i32, u32, u32)> {
events
.iter()
.map(|e| (e.start.year(), e.start.month(), e.start.day()))
.collect()
}
#[test]
fn biweekly_tue_thu_alternating_weeks() {
let result = expand_rrule(
"FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH",
"2026-01-06T10:00:00",
60,
"UTC",
None,
Some(8),
)
.expect("should expand biweekly TU,TH");
assert_eq!(result.len(), 8, "should produce 8 instances");
let d = dates(&result);
assert_eq!(d[0], (2026, 1, 6), "1st: Tue Jan 6");
assert_eq!(d[1], (2026, 1, 8), "2nd: Thu Jan 8");
assert_eq!(d[2], (2026, 1, 20), "3rd: Tue Jan 20");
assert_eq!(d[3], (2026, 1, 22), "4th: Thu Jan 22");
assert_eq!(d[4], (2026, 2, 3), "5th: Tue Feb 3");
assert_eq!(d[5], (2026, 2, 5), "6th: Thu Feb 5");
assert_eq!(d[6], (2026, 2, 17), "7th: Tue Feb 17");
assert_eq!(d[7], (2026, 2, 19), "8th: Thu Feb 19");
}
#[test]
fn yearly_june_15() {
let result = expand_rrule(
"FREQ=YEARLY;BYMONTH=6;BYMONTHDAY=15",
"2026-06-15T12:00:00",
60,
"UTC",
None,
Some(4),
)
.expect("should expand yearly June 15");
assert_eq!(result.len(), 4);
let d = dates(&result);
assert_eq!(d[0], (2026, 6, 15));
assert_eq!(d[1], (2027, 6, 15));
assert_eq!(d[2], (2028, 6, 15));
assert_eq!(d[3], (2029, 6, 15));
}
#[test]
fn leap_year_feb_29() {
let result = expand_rrule(
"FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=29",
"2024-02-29T08:00:00",
60,
"UTC",
None,
Some(3),
)
.expect("should expand yearly Feb 29");
assert_eq!(result.len(), 3, "should produce 3 leap-year instances");
let d = dates(&result);
assert_eq!(d[0], (2024, 2, 29), "first: 2024 leap year");
assert_eq!(
d[1],
(2028, 2, 29),
"second: 2028 leap year (skipped 2025-2027)"
);
assert_eq!(
d[2],
(2032, 2, 29),
"third: 2032 leap year (skipped 2029-2031)"
);
}
#[test]
fn exdate_excludes_specific_dates() {
let result = truth_engine::expander::expand_rrule_with_exdates(
"FREQ=WEEKLY;BYDAY=TU",
"2026-03-03T10:00:00",
60,
"UTC",
Some("2026-04-07T23:59:59"),
None,
&[
"2026-03-10T10:00:00",
"2026-03-17T10:00:00",
"2026-03-31T10:00:00",
],
)
.expect("should expand with exdates");
let d = dates(&result);
assert_eq!(d.len(), 3, "6 Tuesdays minus 3 excluded = 3 remaining");
assert_eq!(d[0], (2026, 3, 3), "Tue Mar 3 — kept");
assert_eq!(d[1], (2026, 3, 24), "Tue Mar 24 — kept");
assert_eq!(d[2], (2026, 4, 7), "Tue Apr 7 — kept");
let excluded = vec![(2026, 3, 10), (2026, 3, 17), (2026, 3, 31)];
for ex in &excluded {
assert!(
!d.contains(ex),
"date {:?} should be excluded by EXDATE",
ex
);
}
}
#[test]
fn rfc5545_count_limit_daily_five() {
let result = expand_rrule(
"FREQ=DAILY;COUNT=5",
"2026-06-01T08:00:00",
45,
"America/New_York",
None,
None,
)
.expect("should expand daily count 5");
assert_eq!(result.len(), 5, "COUNT=5 must produce exactly 5 instances");
let d = dates(&result);
for i in 0..5u32 {
assert_eq!(d[i as usize], (2026, 6, 1 + i), "day {} mismatch", i);
}
assert_eq!(d[0], (2026, 6, 1));
assert_eq!(d[4], (2026, 6, 5));
}
#[test]
fn monthly_interval_three_quarterly() {
let result = expand_rrule(
"FREQ=MONTHLY;INTERVAL=3",
"2026-03-15T09:00:00",
60,
"UTC",
None,
Some(5),
)
.expect("should expand quarterly");
assert_eq!(result.len(), 5);
let d = dates(&result);
assert_eq!(d[0], (2026, 3, 15), "Mar 2026");
assert_eq!(d[1], (2026, 6, 15), "Jun 2026");
assert_eq!(d[2], (2026, 9, 15), "Sep 2026");
assert_eq!(d[3], (2026, 12, 15), "Dec 2026");
assert_eq!(d[4], (2027, 3, 15), "Mar 2027");
}
#[test]
fn last_weekday_of_month_bysetpos_neg1() {
let result = expand_rrule(
"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
"2026-01-30T17:00:00",
60,
"UTC",
None,
Some(6),
)
.expect("should expand last weekday of month");
assert_eq!(result.len(), 6);
let d = dates(&result);
assert_eq!(d[0], (2026, 1, 30), "Last weekday of Jan 2026 = Fri 30");
assert_eq!(d[1], (2026, 2, 27), "Last weekday of Feb 2026 = Fri 27");
assert_eq!(d[2], (2026, 3, 31), "Last weekday of Mar 2026 = Tue 31");
assert_eq!(d[3], (2026, 4, 30), "Last weekday of Apr 2026 = Thu 30");
assert_eq!(d[4], (2026, 5, 29), "Last weekday of May 2026 = Fri 29");
assert_eq!(d[5], (2026, 6, 30), "Last weekday of Jun 2026 = Tue 30");
}
#[test]
fn second_tuesday_of_jan_and_jun() {
let result = expand_rrule(
"FREQ=MONTHLY;BYMONTH=1,6;BYDAY=TU;BYSETPOS=2",
"2026-01-13T14:00:00",
60,
"UTC",
None,
Some(4),
)
.expect("should expand 2nd Tuesday of Jan & Jun");
assert_eq!(result.len(), 4);
let d = dates(&result);
assert_eq!(d[0], (2026, 1, 13), "2nd Tue of Jan 2026");
assert_eq!(d[1], (2026, 6, 9), "2nd Tue of Jun 2026");
assert_eq!(d[2], (2027, 1, 12), "2nd Tue of Jan 2027");
assert_eq!(d[3], (2027, 6, 8), "2nd Tue of Jun 2027");
}