1use crate::error::*;
2use crate::parse::parse;
3use crate::types::*;
4use std::iter::Iterator;
5use std::str::FromStr;
6use time::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset};
7
8#[derive(Debug, PartialEq, Clone)]
11pub struct Schedule(ParsedSchedule);
12
13impl Schedule {
14 #[allow(dead_code)]
15 pub fn iter(&self) -> Result<ScheduleIter, Error> {
16 let Schedule(schedule) = self;
17 let iter = ScheduleIter {
18 schedule: schedule.clone(),
19 current: OffsetDateTime::now_local().map_err(Error::IndeterminateOffset)?,
20 skip_outdated: true,
21 offset: None,
22 };
23 Ok(iter)
24 }
25}
26
27impl FromStr for Schedule {
28 type Err = Error;
29
30 fn from_str(expression: &str) -> Result<Self, Self::Err> {
40 Ok(Schedule(parse(expression)?))
41 }
42}
43
44impl std::ops::Add<Schedule> for Schedule {
45 type Output = MultiSchedule;
46
47 fn add(self, other: Self) -> Self::Output {
48 let Schedule(first) = self;
49 let Schedule(second) = other;
50 MultiSchedule(vec![first, second])
51 }
52}
53
54#[derive(Clone)]
56pub struct ScheduleIter {
57 schedule: ParsedSchedule,
58 current: OffsetDateTime,
59 skip_outdated: bool,
60 offset: Option<UtcOffset>,
61}
62
63impl ScheduleIter {
64 pub fn skip_outdated(mut self, skip: bool) -> ScheduleIter {
68 self.skip_outdated = skip;
69 self
70 }
71
72 pub fn assume_offset(mut self, offset: UtcOffset) -> ScheduleIter {
76 self.offset = Some(offset);
77 self
78 }
79
80 pub fn use_local_offset(mut self) -> ScheduleIter {
84 self.offset = None;
85 self
86 }
87}
88
89impl Iterator for ScheduleIter {
90 type Item = Result<OffsetDateTime, Error>;
91
92 fn next(&mut self) -> Option<Self::Item> {
93 if let Some(offset) = self.offset {
94 self.current = self.current.to_offset(offset);
95 }
96
97 if self.skip_outdated {
98 let mut now = match OffsetDateTime::now_local().map_err(Error::IndeterminateOffset) {
99 Ok(n) => n,
100 Err(e) => return Some(Err(e)),
101 };
102
103 if let Some(offset) = self.offset {
104 now = now.to_offset(offset);
105 }
106
107 if now > self.current {
108 self.current = now;
109 }
110 }
111
112 let candidates: Vec<OffsetDateTime> = compute_dates(self.current, &self.schedule);
115
116 let next_date = candidates
121 .iter()
122 .min_by_key(|d| **d - self.current)
123 .unwrap();
124
125 self.current = *next_date;
126
127 Some(Ok(*next_date))
128 }
129}
130
131#[derive(Debug, PartialEq, Clone)]
136pub struct MultiSchedule(Vec<ParsedSchedule>);
137
138impl MultiSchedule {
139 #[allow(dead_code)]
140 pub fn iter(&self) -> Result<MultiScheduleIter, Error> {
141 let MultiSchedule(schedules) = self;
142 let iter = MultiScheduleIter {
143 schedules: &schedules,
144 current: OffsetDateTime::now_local().map_err(Error::IndeterminateOffset)?,
145 skip_outdated: true,
146 offset: None,
147 };
148 Ok(iter)
149 }
150}
151
152impl From<Schedule> for MultiSchedule {
153 fn from(schedule: Schedule) -> Self {
154 let Schedule(schedule) = schedule;
155 MultiSchedule(vec![schedule])
156 }
157}
158
159impl std::ops::Add<Schedule> for MultiSchedule {
160 type Output = Self;
161
162 fn add(self, other: Schedule) -> Self {
163 let MultiSchedule(mut schedules) = self;
164 let Schedule(schedule) = other;
165 schedules.push(schedule);
166 MultiSchedule(schedules)
167 }
168}
169
170impl std::ops::Add<MultiSchedule> for MultiSchedule {
171 type Output = Self;
172
173 fn add(self, other: MultiSchedule) -> Self {
174 let MultiSchedule(mut schedules) = self;
175 let MultiSchedule(mut other_schedules) = other;
176 schedules.append(&mut other_schedules);
177 MultiSchedule(schedules)
178 }
179}
180
181impl std::ops::AddAssign<Schedule> for MultiSchedule {
182 fn add_assign(&mut self, other: Schedule) {
183 let MultiSchedule(schedules) = self;
184 let Schedule(schedule) = other;
185 schedules.push(schedule);
186 }
187}
188
189impl std::ops::AddAssign<MultiSchedule> for MultiSchedule {
190 fn add_assign(&mut self, other: MultiSchedule) {
191 let MultiSchedule(schedules) = self;
192 let MultiSchedule(mut other_schedules) = other;
193 schedules.append(&mut other_schedules);
194 }
195}
196
197#[derive(Clone)]
199pub struct MultiScheduleIter<'a> {
200 schedules: &'a [ParsedSchedule],
201 current: OffsetDateTime,
202 skip_outdated: bool,
203 offset: Option<UtcOffset>,
204}
205
206impl<'a> MultiScheduleIter<'a> {
207 pub fn skip_outdated(mut self, skip: bool) -> MultiScheduleIter<'a> {
211 self.skip_outdated = skip;
212 self
213 }
214 pub fn assume_offset(mut self, offset: UtcOffset) -> MultiScheduleIter<'a> {
218 self.offset = Some(offset);
219 self
220 }
221
222 pub fn use_local_offset(mut self) -> MultiScheduleIter<'a> {
226 self.offset = None;
227 self
228 }
229}
230
231impl<'a> Iterator for MultiScheduleIter<'a> {
232 type Item = Result<OffsetDateTime, Error>;
233
234 fn next(&mut self) -> Option<Self::Item> {
235 if let Some(offset) = self.offset {
236 self.current = self.current.to_offset(offset);
237 }
238
239 if self.skip_outdated {
240 let mut now = match OffsetDateTime::now_local().map_err(Error::IndeterminateOffset) {
241 Ok(n) => n,
242 Err(e) => return Some(Err(e)),
243 };
244
245 if let Some(offset) = self.offset {
246 now = now.to_offset(offset);
247 }
248
249 if now > self.current {
250 self.current = now;
251 }
252 }
253
254 let mut candidates: Vec<OffsetDateTime> = vec![];
257
258 for schedule in self.schedules {
259 candidates.append(&mut compute_dates(self.current, schedule));
260 }
261
262 let next_date = candidates
267 .iter()
268 .min_by_key(|d| **d - self.current)
269 .unwrap();
270
271 self.current = *next_date;
272
273 Some(Ok(*next_date))
274 }
275}
276
277fn compute_dates(base: OffsetDateTime, spec: &ParsedSchedule) -> Vec<OffsetDateTime> {
279 let mut candidates = vec![];
280 let today = base.date();
281 let offset = base.offset();
282
283 for time in &spec.times {
285 for i in 0..=6 {
287 let mut date =
288 PrimitiveDateTime::new(today + Duration::days(i), *time).assume_offset(offset);
289
290 if date <= base {
291 date += Duration::weeks(1);
292 }
293
294 candidates.push(date);
295 }
296 }
297
298 if let Some(ref days) = spec.days {
302 let weeks = spec.weeks;
303
304 candidates = candidates
305 .into_iter()
306 .filter(|c| days.iter().any(|x| x.0 == c.weekday()))
307 .collect();
308
309 for candidate in &mut candidates {
310 let day_modifier = days.iter().find(|x| x.0 == candidate.weekday()).unwrap().1;
311
312 while !check_date_validity(candidate, day_modifier, weeks) {
313 *candidate += Duration::weeks(1);
314 }
315 }
316 }
317
318 candidates
320}
321
322fn check_date_validity(
325 date: &OffsetDateTime,
326 weekday_mod: Option<WeekdayModifier>,
327 week_mod: Option<WeekVariant>,
328) -> bool {
329 let is_correct_day = match weekday_mod {
330 Some(modifier) => {
331 let day = date.day();
332
333 match modifier {
334 WeekdayModifier::First => day <= 7,
335 WeekdayModifier::Second => day > 7 && day <= 14,
336 WeekdayModifier::Third => day > 14 && day <= 21,
337 WeekdayModifier::Fourth => day > 21 && day <= 28,
338 WeekdayModifier::Last => date.month() != (*date + Duration::weeks(1)).month(),
339 }
340 }
341 None => true,
342 };
343
344 let is_correct_week = match week_mod {
345 Some(modifier) => {
346 let week = date.date().iso_week();
347
348 match modifier {
349 WeekVariant::Even => week % 2 == 0,
350 WeekVariant::Odd => week % 2 != 0,
351 }
352 }
353 None => true,
354 };
355
356 is_correct_day && is_correct_week
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use time::macros::{datetime, offset, time};
363 use time::Weekday;
364
365 #[test]
366 fn test_compute_dates_1() {
367 let base = datetime!(2021-06-04 13:38:00 UTC);
368 let spec = ParsedSchedule {
369 times: vec![time!(12:00:00), time!(18:00:00)],
370 days: None,
371 weeks: None,
372 };
373 let result = vec![
374 datetime!(2021-06-11 12:00:00 UTC),
375 datetime!(2021-06-05 12:00:00 UTC),
376 datetime!(2021-06-06 12:00:00 UTC),
377 datetime!(2021-06-07 12:00:00 UTC),
378 datetime!(2021-06-08 12:00:00 UTC),
379 datetime!(2021-06-09 12:00:00 UTC),
380 datetime!(2021-06-10 12:00:00 UTC),
381 datetime!(2021-06-04 18:00:00 UTC),
382 datetime!(2021-06-05 18:00:00 UTC),
383 datetime!(2021-06-06 18:00:00 UTC),
384 datetime!(2021-06-07 18:00:00 UTC),
385 datetime!(2021-06-08 18:00:00 UTC),
386 datetime!(2021-06-09 18:00:00 UTC),
387 datetime!(2021-06-10 18:00:00 UTC),
388 ];
389 assert_eq!(compute_dates(base, &spec), result);
390 }
391
392 #[test]
393 fn test_compute_dates_2() {
394 let base = datetime!(2021-06-04 13:38:00 UTC);
395 let spec = ParsedSchedule {
396 times: vec![time!(18:00:00)],
397 days: Some(vec![(Weekday::Monday, None), (Weekday::Thursday, None)]),
398 weeks: None,
399 };
400 let result = vec![
401 datetime!(2021-06-07 18:00:00 UTC),
402 datetime!(2021-06-10 18:00:00 UTC),
403 ];
404 assert_eq!(compute_dates(base, &spec), result);
405 }
406
407 #[test]
408 fn test_compute_dates_3() {
409 let base = datetime!(2021-06-04 13:38:00 UTC);
410 let spec = ParsedSchedule {
411 times: vec![time!(18:00:00)],
412 days: Some(vec![
413 (Weekday::Monday, Some(WeekdayModifier::Second)),
414 (Weekday::Thursday, None),
415 ]),
416 weeks: None,
417 };
418 let result = vec![
419 datetime!(2021-06-14 18:00:00 UTC),
420 datetime!(2021-06-10 18:00:00 UTC),
421 ];
422 assert_eq!(compute_dates(base, &spec), result);
423 }
424
425 #[test]
426 fn test_compute_dates_4() {
427 let base = datetime!(2021-06-04 13:38:00 UTC);
428 let spec = ParsedSchedule {
429 times: vec![time!(12:00:00), time!(18:00:00)],
430 days: Some(vec![
431 (Weekday::Friday, Some(WeekdayModifier::First)),
432 (Weekday::Thursday, None),
433 ]),
434 weeks: None,
435 };
436 let result = vec![
437 datetime!(2021-07-02 12:00:00 UTC),
438 datetime!(2021-06-10 12:00:00 UTC),
439 datetime!(2021-06-04 18:00:00 UTC),
440 datetime!(2021-06-10 18:00:00 UTC),
441 ];
442 assert_eq!(compute_dates(base, &spec), result);
443 }
444
445 #[test]
446 fn test_compute_dates_5() {
447 let base = datetime!(2021-06-12 13:38:00 UTC);
448 let spec = ParsedSchedule {
449 times: vec![time!(06:00:00), time!(12:00:00), time!(18:00:00)],
450 days: Some(vec![
451 (Weekday::Friday, Some(WeekdayModifier::First)),
452 (Weekday::Thursday, None),
453 (Weekday::Monday, Some(WeekdayModifier::Third)),
454 ]),
455 weeks: None,
456 };
457 let result = vec![
458 datetime!(2021-06-21 06:00:00 UTC),
459 datetime!(2021-06-17 06:00:00 UTC),
460 datetime!(2021-07-02 06:00:00 UTC),
461 datetime!(2021-06-21 12:00:00 UTC),
462 datetime!(2021-06-17 12:00:00 UTC),
463 datetime!(2021-07-02 12:00:00 UTC),
464 datetime!(2021-06-21 18:00:00 UTC),
465 datetime!(2021-06-17 18:00:00 UTC),
466 datetime!(2021-07-02 18:00:00 UTC),
467 ];
468
469 assert_eq!(compute_dates(base, &spec), result);
470 }
471
472 #[test]
473 fn test_schedule_iteration_1() {
474 let iterator = ScheduleIter {
475 current: datetime!(2021-06-09 13:00:00 UTC),
476 schedule: ParsedSchedule {
477 times: vec![time!(01:00:00)],
478 days: None,
479 weeks: None,
480 },
481 skip_outdated: false,
482 offset: None,
483 };
484
485 let result = vec![
486 Ok(datetime!(2021-06-10 01:00:00 UTC)),
487 Ok(datetime!(2021-06-11 01:00:00 UTC)),
488 Ok(datetime!(2021-06-12 01:00:00 UTC)),
489 ];
490
491 assert_eq!(
492 iterator
493 .take(3)
494 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
495 result
496 );
497 }
498
499 #[test]
500 fn test_schedule_iteration_2() {
501 let iterator = ScheduleIter {
502 current: datetime!(2021-06-09 13:00:00 UTC),
503 schedule: ParsedSchedule {
504 times: vec![time!(13:00:00)],
505 days: Some(vec![(Weekday::Monday, None)]),
506 weeks: None,
507 },
508 skip_outdated: false,
509 offset: None,
510 };
511
512 let result = vec![
513 Ok(datetime!(2021-06-14 13:00:00 UTC)),
514 Ok(datetime!(2021-06-21 13:00:00 UTC)),
515 Ok(datetime!(2021-06-28 13:00:00 UTC)),
516 ];
517
518 assert_eq!(
519 iterator
520 .take(3)
521 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
522 result
523 );
524 }
525
526 #[test]
527 fn test_schedule_iteration_3() {
528 let iterator = ScheduleIter {
529 current: datetime!(2021-06-09 13:00:00 UTC),
530 schedule: ParsedSchedule {
531 times: vec![time!(06:00:00), time!(13:00:00)],
532 days: Some(vec![
533 (Weekday::Monday, Some(WeekdayModifier::Third)),
534 (Weekday::Thursday, None),
535 ]),
536 weeks: None,
537 },
538 skip_outdated: false,
539 offset: None,
540 };
541
542 let result = vec![
543 Ok(datetime!(2021-06-10 06:00:00 UTC)),
544 Ok(datetime!(2021-06-10 13:00:00 UTC)),
545 Ok(datetime!(2021-06-17 06:00:00 UTC)),
546 Ok(datetime!(2021-06-17 13:00:00 UTC)),
547 Ok(datetime!(2021-06-21 06:00:00 UTC)),
548 Ok(datetime!(2021-06-21 13:00:00 UTC)),
549 Ok(datetime!(2021-06-24 06:00:00 UTC)),
550 Ok(datetime!(2021-06-24 13:00:00 UTC)),
551 ];
552
553 assert_eq!(
554 iterator
555 .take(8)
556 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
557 result
558 );
559 }
560
561 #[test]
562 fn test_schedule_iteration_4() {
563 let iterator = ScheduleIter {
564 current: datetime!(2021-06-09 13:00:00 UTC),
565 schedule: ParsedSchedule {
566 times: vec![time!(06:00:00), time!(13:00:00)],
567 days: Some(vec![
568 (Weekday::Monday, Some(WeekdayModifier::Third)),
569 (Weekday::Thursday, None),
570 ]),
571 weeks: None,
572 },
573 skip_outdated: false,
574 offset: Some(offset!(+3)),
575 };
576
577 let result = vec![
578 Ok(datetime!(2021-06-10 06:00:00 +3)),
579 Ok(datetime!(2021-06-10 13:00:00 +3)),
580 Ok(datetime!(2021-06-17 06:00:00 +3)),
581 Ok(datetime!(2021-06-17 13:00:00 +3)),
582 Ok(datetime!(2021-06-21 06:00:00 +3)),
583 Ok(datetime!(2021-06-21 13:00:00 +3)),
584 Ok(datetime!(2021-06-24 06:00:00 +3)),
585 Ok(datetime!(2021-06-24 13:00:00 +3)),
586 ];
587
588 assert_eq!(
589 iterator
590 .take(8)
591 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
592 result
593 );
594 }
595
596 #[test]
597 fn test_schedule_iteration_5() {
598 let iterator = MultiScheduleIter {
599 current: datetime!(2021-06-09 13:00:00 UTC),
600 schedules: &vec![
601 ParsedSchedule {
602 times: vec![time!(06:00:00), time!(13:00:00)],
603 days: Some(vec![
604 (Weekday::Monday, Some(WeekdayModifier::Third)),
605 (Weekday::Thursday, None),
606 ]),
607 weeks: None,
608 },
609 ParsedSchedule {
610 times: vec![time!(18:00:00)],
611 days: Some(vec![(Weekday::Saturday, Some(WeekdayModifier::Fourth))]),
612 weeks: Some(WeekVariant::Odd),
613 },
614 ],
615 skip_outdated: false,
616 offset: None,
617 };
618
619 let result = vec![
620 Ok(datetime!(2021-06-10 06:00:00 UTC)),
621 Ok(datetime!(2021-06-10 13:00:00 UTC)),
622 Ok(datetime!(2021-06-17 06:00:00 UTC)),
623 Ok(datetime!(2021-06-17 13:00:00 UTC)),
624 Ok(datetime!(2021-06-21 06:00:00 UTC)),
625 Ok(datetime!(2021-06-21 13:00:00 UTC)),
626 Ok(datetime!(2021-06-24 06:00:00 UTC)),
627 Ok(datetime!(2021-06-24 13:00:00 UTC)),
628 Ok(datetime!(2021-06-26 18:00:00 UTC)),
629 Ok(datetime!(2021-07-01 06:00:00 UTC)),
630 Ok(datetime!(2021-07-01 13:00:00 UTC)),
631 ];
632
633 assert_eq!(
634 iterator
635 .take(11)
636 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
637 result
638 );
639 }
640
641 #[test]
642 fn test_schedule_iteration_6() {
643 let iterator = MultiScheduleIter {
644 current: datetime!(2021-06-18 13:00:00 UTC),
645 schedules: &vec![
646 ParsedSchedule {
647 times: vec![time!(06:00:00), time!(18:00:00)],
648 days: Some(vec![
649 (Weekday::Monday, Some(WeekdayModifier::Last)),
650 (Weekday::Thursday, None),
651 ]),
652 weeks: None,
653 },
654 ParsedSchedule {
655 times: vec![time!(18:00:00)],
656 days: Some(vec![(Weekday::Saturday, Some(WeekdayModifier::Fourth))]),
657 weeks: None,
658 },
659 ],
660 skip_outdated: false,
661 offset: None,
662 };
663
664 let result = vec![
665 Ok(datetime!(2021-06-24 06:00:00 UTC)),
666 Ok(datetime!(2021-06-24 18:00:00 UTC)),
667 Ok(datetime!(2021-06-26 18:00:00 UTC)),
668 Ok(datetime!(2021-06-28 06:00:00 UTC)),
669 Ok(datetime!(2021-06-28 18:00:00 UTC)),
670 Ok(datetime!(2021-07-01 06:00:00 UTC)),
671 Ok(datetime!(2021-07-01 18:00:00 UTC)),
672 Ok(datetime!(2021-07-08 06:00:00 UTC)),
673 Ok(datetime!(2021-07-08 18:00:00 UTC)),
674 Ok(datetime!(2021-07-15 06:00:00 UTC)),
675 Ok(datetime!(2021-07-15 18:00:00 UTC)),
676 Ok(datetime!(2021-07-22 06:00:00 UTC)),
677 Ok(datetime!(2021-07-22 18:00:00 UTC)),
678 Ok(datetime!(2021-07-24 18:00:00 UTC)),
679 Ok(datetime!(2021-07-26 06:00:00 UTC)),
680 Ok(datetime!(2021-07-26 18:00:00 UTC)),
681 Ok(datetime!(2021-07-29 06:00:00 UTC)),
682 Ok(datetime!(2021-07-29 18:00:00 UTC)),
683 ];
684
685 assert_eq!(
686 iterator
687 .take(18)
688 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
689 result
690 );
691 }
692
693 #[test]
694 fn test_schedule_iteration_7() {
695 let iterator = MultiScheduleIter {
696 current: datetime!(2021-06-18 13:00:00 UTC),
697 schedules: &vec![
698 ParsedSchedule {
699 times: vec![time!(06:00:00), time!(18:00:00)],
700 days: Some(vec![
701 (Weekday::Monday, Some(WeekdayModifier::Last)),
702 (Weekday::Thursday, None),
703 ]),
704 weeks: None,
705 },
706 ParsedSchedule {
707 times: vec![time!(18:00:00)],
708 days: Some(vec![(Weekday::Saturday, Some(WeekdayModifier::Fourth))]),
709 weeks: None,
710 },
711 ],
712 skip_outdated: false,
713 offset: Some(offset!(+2:30)),
714 };
715
716 let result = vec![
717 Ok(datetime!(2021-06-24 06:00:00 +2:30)),
718 Ok(datetime!(2021-06-24 18:00:00 +2:30)),
719 Ok(datetime!(2021-06-26 18:00:00 +2:30)),
720 Ok(datetime!(2021-06-28 06:00:00 +2:30)),
721 Ok(datetime!(2021-06-28 18:00:00 +2:30)),
722 Ok(datetime!(2021-07-01 06:00:00 +2:30)),
723 Ok(datetime!(2021-07-01 18:00:00 +2:30)),
724 ];
725
726 assert_eq!(
727 iterator
728 .take(7)
729 .collect::<Vec<Result<OffsetDateTime, Error>>>(),
730 result
731 );
732 }
733
734 #[test]
735 fn test_add_two_schedules() {
736 let sched1 = Schedule(ParsedSchedule {
737 times: vec![time!(06:00:00), time!(13:00:00)],
738 days: Some(vec![
739 (Weekday::Monday, Some(WeekdayModifier::Third)),
740 (Weekday::Thursday, None),
741 ]),
742 weeks: None,
743 });
744
745 let sched2 = Schedule(ParsedSchedule {
746 times: vec![time!(18:00:00)],
747 days: Some(vec![(Weekday::Saturday, Some(WeekdayModifier::Fourth))]),
748 weeks: Some(WeekVariant::Odd),
749 });
750
751 let multi_sched = MultiSchedule(vec![
752 ParsedSchedule {
753 times: vec![time!(06:00:00), time!(13:00:00)],
754 days: Some(vec![
755 (Weekday::Monday, Some(WeekdayModifier::Third)),
756 (Weekday::Thursday, None),
757 ]),
758 weeks: None,
759 },
760 ParsedSchedule {
761 times: vec![time!(18:00:00)],
762 days: Some(vec![(Weekday::Saturday, Some(WeekdayModifier::Fourth))]),
763 weeks: Some(WeekVariant::Odd),
764 },
765 ]);
766
767 assert_eq!(multi_sched, sched1 + sched2)
768 }
769}