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}