ikal/
iter.rs

1pub trait Recurring: Clone {
2    fn dtstart(&self) -> Option<&crate::Date> {
3        None
4    }
5
6    fn exdate(&self) -> &[crate::Date];
7
8    fn set_dtstart(&mut self, _dtstart: crate::Date) {}
9
10    fn dtend(&self) -> Option<&crate::Date> {
11        None
12    }
13
14    fn set_dtend(&mut self, _dtend: crate::Date) {}
15
16    fn due(&self) -> Option<&crate::Date> {
17        None
18    }
19
20    fn set_due(&mut self, _dtend: crate::Date) {}
21
22    fn rrule(&self) -> Option<&crate::Recur>;
23    fn set_rrule(&mut self, rrule: crate::Recur);
24}
25
26impl Recurring for crate::VEvent {
27    fn dtstart(&self) -> Option<&crate::Date> {
28        Some(&self.dtstart)
29    }
30
31    fn exdate(&self) -> &[crate::Date] {
32        &self.exdate
33    }
34
35    fn set_dtstart(&mut self, dtstart: crate::Date) {
36        self.dtstart = dtstart;
37    }
38
39    fn dtend(&self) -> Option<&crate::Date> {
40        self.dtend.as_ref()
41    }
42
43    fn set_dtend(&mut self, dtend: crate::Date) {
44        self.dtend = Some(dtend);
45    }
46
47    fn rrule(&self) -> Option<&crate::Recur> {
48        self.rrule.as_ref()
49    }
50
51    fn set_rrule(&mut self, rrule: crate::Recur) {
52        self.rrule = Some(rrule);
53    }
54}
55
56impl Recurring for crate::VJournal {
57    fn dtstart(&self) -> Option<&crate::Date> {
58        Some(&self.dtstart)
59    }
60
61    fn exdate(&self) -> &[crate::Date] {
62        &self.exdate
63    }
64
65    fn set_dtstart(&mut self, dtstart: crate::Date) {
66        self.dtstart = dtstart;
67    }
68
69    fn rrule(&self) -> Option<&crate::Recur> {
70        self.rrule.as_ref()
71    }
72
73    fn set_rrule(&mut self, rrule: crate::Recur) {
74        self.rrule = Some(rrule);
75    }
76}
77
78impl Recurring for crate::VTodo {
79    fn dtstart(&self) -> Option<&crate::Date> {
80        self.dtstart.as_ref()
81    }
82
83    fn exdate(&self) -> &[crate::Date] {
84        &self.exdate
85    }
86
87    fn set_dtstart(&mut self, dtstart: crate::Date) {
88        self.dtstart = Some(dtstart);
89    }
90
91    fn due(&self) -> Option<&crate::Date> {
92        self.due.as_ref()
93    }
94
95    fn set_due(&mut self, due: crate::Date) {
96        self.due = Some(due);
97    }
98
99    fn rrule(&self) -> Option<&crate::Recur> {
100        self.rrule.as_ref()
101    }
102
103    fn set_rrule(&mut self, rrule: crate::Recur) {
104        self.rrule = Some(rrule);
105    }
106}
107
108pub struct Recur<T: Recurring> {
109    item: T,
110}
111
112impl<T: Recurring> Recur<T> {
113    pub(crate) fn from(item: &T) -> Self {
114        Self { item: item.clone() }
115    }
116
117    pub fn between<D: Into<crate::Date> + Copy>(self, start: D, end: D) -> impl Iterator<Item = T> {
118        self.skip_while(move |x| x.dtstart().unwrap() < &start.into())
119            .take_while(move |x| x.dtstart().unwrap() < &end.into())
120    }
121
122    pub fn at<D: Into<crate::Date> + Copy>(self, date: D) -> impl Iterator<Item = T> {
123        let delta = chrono::TimeDelta::days(1);
124        self.between(date.into(), date.into() + delta)
125    }
126
127    pub fn after<D: Into<crate::Date> + Copy>(self, date: D) -> impl Iterator<Item = T> {
128        self.skip_while(move |x| x.dtstart().unwrap() < &date.into())
129    }
130}
131
132impl<T: Recurring> Iterator for Recur<T> {
133    type Item = T;
134
135    fn next(&mut self) -> Option<Self::Item> {
136        let current = self.item.clone();
137
138        let mut next = self.item.clone();
139
140        loop {
141            let dtstart = next.dtstart()?;
142            let mut rrule = next.rrule()?.clone();
143
144            if let Some(until) = &rrule.until {
145                if dtstart.date_naive() > until.date_naive() {
146                    return None;
147                }
148            }
149
150            let dtstart = rrule.clone() + *dtstart;
151            next.set_dtstart(dtstart);
152
153            if let Some(dtend) = next.dtend() {
154                let dtend = rrule.clone() + *dtend;
155                next.set_dtend(dtend);
156            }
157
158            next.set_rrule(rrule.clone());
159
160            if next.exdate().contains(&dtstart) {
161                continue;
162            }
163
164            if let Some(count) = rrule.count.as_mut() {
165                *count = count.checked_sub(1)?;
166                next.set_rrule(rrule.clone());
167            }
168
169            break;
170        }
171
172        self.item = next;
173
174        Some(current)
175    }
176}
177
178#[cfg(test)]
179mod test {
180    #[test]
181    fn at() {
182        let now = chrono::Local::now().date_naive().into();
183
184        let event = crate::vevent! {
185            dtstart: "20240101",
186            dtend: "20240101",
187            rrule: {
188                freq: Daily,
189                interval: 1,
190            }
191        }
192        .unwrap();
193
194        let next = event.recurrent().at(now).next().unwrap();
195
196        assert_eq!(next.dtstart, now);
197        assert_eq!(next.dtend, Some(now));
198    }
199
200    #[test]
201    fn count() {
202        let event = crate::vevent! {
203            rrule: {
204                freq: Weekly,
205                interval: 1,
206                count: 10,
207            }
208        }
209        .unwrap();
210
211        let events = event.recurrent();
212
213        assert_eq!(events.count(), 10);
214    }
215
216    #[test]
217    fn after() {
218        let now: crate::Date = chrono::Local::now().into();
219
220        let event = crate::vevent! {
221            rrule: {
222                freq: Monthly,
223                interval: 1,
224                until: now,
225            }
226        }
227        .unwrap();
228
229        let mut events = event.recurrent().after(now);
230
231        assert_eq!(events.next(), None);
232    }
233
234    #[test]
235    fn between() {
236        let now = chrono::Local::now();
237
238        let event = crate::vevent! {
239            rrule: {
240                freq: Yearly,
241                interval: 10,
242            }
243        }
244        .unwrap();
245
246        use chrono::Datelike as _;
247        let end = now.with_year(now.year() + 20).unwrap();
248        let events = event.recurrent().between(now, end);
249
250        assert_eq!(events.count(), 2);
251    }
252
253    #[test]
254    fn exdate() {
255        let event = crate::vevent! {
256            dtstart: "20240101",
257            rrule: {
258                freq: Yearly,
259                interval: 1,
260                count: 10,
261            },
262            exdate: ["20250101"],
263        }
264        .unwrap();
265
266        let mut events = event.recurrent();
267
268        assert_eq!(events.nth(1).unwrap().dtstart, "20260101".parse().unwrap());
269    }
270
271    #[test]
272    fn vjournal() {
273        let vjournal = crate::vjournal! {
274            rrule: {
275                freq: Daily,
276                count: 2,
277            }
278        }
279        .unwrap();
280
281        let iter = vjournal.recurrent();
282
283        assert_eq!(iter.count(), 2);
284    }
285
286    #[test]
287    fn vtodo() {
288        let vtodo = crate::vtodo! {
289            dtstart: "20240101",
290            rrule: {
291                freq: Daily,
292                count: 2,
293            }
294        }
295        .unwrap();
296
297        let iter = vtodo.recurrent();
298
299        assert_eq!(iter.count(), 2);
300    }
301}