Skip to main content

ics_core/
query.rs

1use crate::error::{Error, Result};
2use crate::event::VEvent;
3use crate::vcalendar::VCalendar;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum SortKey {
7    Start,
8    End,
9    Summary,
10}
11
12pub fn sort_events(events: &[VEvent], keys: &[SortKey], descending: bool) -> Vec<VEvent> {
13    let mut sorted = events.to_vec();
14    sorted.sort_by(|a, b| {
15        let ord = keys
16            .iter()
17            .map(|key| match key {
18                SortKey::Start => a.dtstart.cmp(&b.dtstart),
19                SortKey::End => a.dtend.cmp(&b.dtend),
20                SortKey::Summary => a.summary.cmp(&b.summary),
21            })
22            .find(|o| o.is_ne())
23            .unwrap_or(std::cmp::Ordering::Equal);
24        if descending { ord.reverse() } else { ord }
25    });
26    sorted
27}
28
29/// Drop events whose `summary` matches the given string. Returns a new
30/// `VCalendar` with the same calendar-level fields and any unrecognized
31/// components preserved. Fails with a `Parse` error if no matching event
32/// is found.
33pub fn remove_event_by_summary(cal: &VCalendar, summary: &str) -> Result<VCalendar> {
34    let remaining: Vec<VEvent> = cal
35        .events
36        .iter()
37        .filter(|e| e.summary != summary)
38        .cloned()
39        .collect();
40    if remaining.len() == cal.events.len() {
41        return Err(Error::parse(format!(
42            "No event found with summary: {summary}"
43        )));
44    }
45    Ok(VCalendar {
46        events: remaining,
47        ..cal.clone()
48    })
49}
50
51/// Drop events whose 1-based index appears in `indices`. Returns a new
52/// `VCalendar`.
53pub fn remove_events_by_indices(cal: &VCalendar, indices: &[usize]) -> Result<VCalendar> {
54    for &idx in indices {
55        if idx == 0 || idx > cal.events.len() {
56            return Err(Error::parse(format!(
57                "Index {idx} out of range (1-{})",
58                cal.events.len()
59            )));
60        }
61    }
62    let remaining: Vec<VEvent> = cal
63        .events
64        .iter()
65        .enumerate()
66        .filter(|(i, _)| !indices.contains(&(i + 1)))
67        .map(|(_, e)| e.clone())
68        .collect();
69    Ok(VCalendar {
70        events: remaining,
71        ..cal.clone()
72    })
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::test_helpers::make_event;
79
80    fn vcal(events: Vec<VEvent>) -> VCalendar {
81        VCalendar {
82            events,
83            ..VCalendar::new("-//makeholiday//EN")
84        }
85    }
86
87    fn unsorted_events() -> Vec<VEvent> {
88        vec![
89            make_event("c", (2026, 5, 3), (2026, 5, 6), "憲法記念日"),
90            make_event("a", (2026, 1, 1), (2026, 1, 2), "元日"),
91            make_event("b", (2026, 2, 11), (2026, 2, 12), "建国記念の日"),
92        ]
93    }
94
95    #[test]
96    fn sort_by_start_asc() {
97        let sorted = sort_events(&unsorted_events(), &[SortKey::Start], false);
98        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
99        assert_eq!(summaries, vec!["元日", "建国記念の日", "憲法記念日"]);
100    }
101
102    #[test]
103    fn sort_by_start_desc() {
104        let sorted = sort_events(&unsorted_events(), &[SortKey::Start], true);
105        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
106        assert_eq!(summaries, vec!["憲法記念日", "建国記念の日", "元日"]);
107    }
108
109    #[test]
110    fn sort_by_end_asc() {
111        let sorted = sort_events(&unsorted_events(), &[SortKey::End], false);
112        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
113        assert_eq!(summaries, vec!["元日", "建国記念の日", "憲法記念日"]);
114    }
115
116    #[test]
117    fn sort_by_summary_asc() {
118        let sorted = sort_events(&unsorted_events(), &[SortKey::Summary], false);
119        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
120        assert_eq!(summaries, vec!["元日", "建国記念の日", "憲法記念日"]);
121    }
122
123    #[test]
124    fn sort_by_summary_desc() {
125        let sorted = sort_events(&unsorted_events(), &[SortKey::Summary], true);
126        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
127        assert_eq!(summaries, vec!["憲法記念日", "建国記念の日", "元日"]);
128    }
129
130    #[test]
131    fn sort_multi_key() {
132        let events = vec![
133            make_event("a", (2026, 1, 1), (2026, 1, 2), "B休日"),
134            make_event("b", (2026, 1, 1), (2026, 1, 2), "A休日"),
135            make_event("c", (2026, 2, 1), (2026, 2, 2), "C休日"),
136        ];
137        let sorted = sort_events(&events, &[SortKey::Start, SortKey::Summary], false);
138        let summaries: Vec<_> = sorted.iter().map(|e| e.summary.as_str()).collect();
139        assert_eq!(summaries, vec!["A休日", "B休日", "C休日"]);
140    }
141
142    #[test]
143    fn remove_by_summary() {
144        let cal = vcal(vec![
145            make_event("a", (2026, 1, 1), (2026, 1, 2), "元日"),
146            make_event("b", (2026, 2, 11), (2026, 2, 12), "建国記念の日"),
147        ]);
148        let updated = remove_event_by_summary(&cal, "元日").unwrap();
149        assert_eq!(updated.events.len(), 1);
150        assert_eq!(updated.events[0].summary, "建国記念の日");
151    }
152
153    #[test]
154    fn remove_by_summary_not_found() {
155        let cal = vcal(vec![make_event("a", (2026, 1, 1), (2026, 1, 2), "元日")]);
156        let result = remove_event_by_summary(&cal, "存在しない");
157        assert!(result.is_err());
158    }
159
160    #[test]
161    fn remove_multiple_by_indices() {
162        let cal = vcal(vec![
163            make_event("a", (2026, 1, 1), (2026, 1, 2), "A"),
164            make_event("b", (2026, 2, 1), (2026, 2, 2), "B"),
165            make_event("c", (2026, 3, 1), (2026, 3, 2), "C"),
166            make_event("d", (2026, 4, 1), (2026, 4, 2), "D"),
167        ]);
168        let updated = remove_events_by_indices(&cal, &[2, 4]).unwrap();
169        let summaries: Vec<_> = updated.events.iter().map(|e| e.summary.as_str()).collect();
170        assert_eq!(summaries, vec!["A", "C"]);
171    }
172}