#![cfg(all(feature = "recurrence", feature = "parser"))]
use chrono::{DateTime, TimeZone};
use icalendar::{Calendar, CalendarComponent, EventLike, Tz};
const TZID_RDATE_EXDATE_STR: &str = r#"BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//test//test//EN
BEGIN:VEVENT
UID:tzid-rdate-exdate-test
DTSTART;TZID=Europe/Berlin:20250101T160000
RRULE:FREQ=DAILY;COUNT=4
RDATE;TZID=America/New_York:20241231T100000
EXDATE;TZID=America/New_York:20250102T100000
END:VEVENT
END:VCALENDAR
"#;
fn naive_dates(dates: &[DateTime<Tz>]) -> Vec<String> {
dates
.iter()
.map(|dt| dt.naive_local().format("%Y-%m-%dT%H:%M:%S").to_string())
.collect()
}
const TEST_CALENDAR_STR: &str = r#"
BEGIN:VCALENDAR
VERSION:2.0
PRODID:bsprodidfortestabc123
BEGIN:VEVENT
DTSTAMP:20050118T211523Z
UID:bsuidfortestabc123
DTSTART;VALUE=DATE:20250101
DTEND;VALUE=DATE:20250101
RDATE;VALUE=DATE:20241231,20241230
SUMMARY:Test Recurrence
EXDATE;VALUE=DATE:20250102,20250103
RRULE:FREQ=DAILY;COUNT=4
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20050118T211523Z
UID:bsuidfortestabc123
DTSTART:20250101T090000Z
DTEND:20250101T110000Z
RDATE:20241231T100000Z
SUMMARY:Test Recurrence
EXDATE:20250102T090000Z,20250103T100000Z
RRULE:FREQ=DAILY;COUNT=4
END:VEVENT
END:VCALENDAR
"#;
#[test]
fn rdate_and_exdate_tzid_params_are_preserved() {
let calendar: Calendar = TZID_RDATE_EXDATE_STR
.parse()
.expect("failed to parse calendar");
let event = match calendar.components.first() {
Some(CalendarComponent::Event(e)) => e,
_ => panic!("first component should be an event"),
};
let rrule_set = event
.get_recurrence()
.expect("event should have a valid recurrence rule");
let dates: Vec<DateTime<Tz>> = rrule_set.all(10).dates;
assert_eq!(dates.len(), 4, "expected 4 occurrences, got: {dates:?}");
let utc_timestamps: Vec<String> = dates
.iter()
.map(|dt| {
dt.with_timezone(&Tz::UTC)
.format("%Y-%m-%dT%H:%M:%SZ")
.to_string()
})
.collect();
assert_eq!(
utc_timestamps,
vec![
"2024-12-31T15:00:00Z",
"2025-01-01T15:00:00Z",
"2025-01-03T15:00:00Z",
"2025-01-04T15:00:00Z",
]
);
}
#[test]
fn parse_recurrence() {
let expected_naive_dates_a = vec![
"2024-12-30T00:00:00",
"2024-12-31T00:00:00",
"2025-01-01T00:00:00",
"2025-01-04T00:00:00",
];
let expected_datetimes_b = vec![
Tz::UTC.with_ymd_and_hms(2024, 12, 31, 10, 0, 0).unwrap(),
Tz::UTC.with_ymd_and_hms(2025, 1, 1, 9, 0, 0).unwrap(),
Tz::UTC.with_ymd_and_hms(2025, 1, 3, 9, 0, 0).unwrap(),
Tz::UTC.with_ymd_and_hms(2025, 1, 4, 9, 0, 0).unwrap(),
];
let calendar: Calendar = TEST_CALENDAR_STR.parse().expect("failed to parse calendar");
assert_eq!(calendar.components.len(), 2);
let event = match calendar.components.first() {
Some(CalendarComponent::Event(event)) => event,
_ => panic!("calendar component should be an event"),
};
let rrules = event
.get_recurrence()
.expect("event should have recurrence rules");
let datetimes: Vec<DateTime<Tz>> = rrules.all(10).dates;
assert_eq!(naive_dates(&datetimes), expected_naive_dates_a);
let event = match calendar.components.get(1) {
Some(CalendarComponent::Event(event)) => event,
_ => panic!("calendar component should be an event"),
};
let rrules = event
.get_recurrence()
.expect("event should have recurrence rules");
let datetimes: Vec<DateTime<Tz>> = rrules.all(10).dates;
assert_eq!(datetimes, expected_datetimes_b);
}