1use chrono::offset::TimeZone;
2use chrono::{DateTime, Datelike, Timelike, Utc};
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::iter::Iterator;
5use std::ops::Bound::{Included, Unbounded};
6
7use crate::ordinal::*;
8use crate::queries::*;
9use crate::time_unit::*;
10
11impl From<Schedule> for String {
12 fn from(schedule: Schedule) -> String {
13 schedule.source
14 }
15}
16
17#[derive(Debug, Clone, Hash, Eq)]
18pub struct Schedule {
20 source: String,
21 fields: ScheduleFields,
22}
23
24impl Schedule {
25 pub(crate) fn new(source: String, fields: ScheduleFields) -> Schedule {
26 Schedule { source, fields }
27 }
28 fn next_after<Z>(&self, after: &DateTime<Z>) -> Option<DateTime<Z>>
29 where
30 Z: TimeZone,
31 {
32 let mut query = NextAfterQuery::from(after);
33 for year in self
34 .fields
35 .years
36 .ordinals()
37 .range((Included(query.year_lower_bound()), Unbounded))
38 .cloned()
39 {
40 let month_start = query.month_lower_bound();
41 if !self.fields.months.ordinals().contains(&month_start) {
42 query.reset_month();
43 }
44 let month_range = (Included(month_start), Included(Months::inclusive_max()));
45 for month in self.fields.months.ordinals().range(month_range).cloned() {
46 let day_of_month_start = query.day_of_month_lower_bound();
47 if !self
48 .fields
49 .days_of_month
50 .ordinals()
51 .contains(&day_of_month_start)
52 {
53 query.reset_day_of_month();
54 }
55 let day_of_month_end = days_in_month(month, year);
56 let day_of_month_range = (Included(day_of_month_start), Included(day_of_month_end));
57
58 'day_loop: for day_of_month in self
59 .fields
60 .days_of_month
61 .ordinals()
62 .range(day_of_month_range)
63 .cloned()
64 {
65 let hour_start = query.hour_lower_bound();
66 if !self.fields.hours.ordinals().contains(&hour_start) {
67 query.reset_hour();
68 }
69 let hour_range = (Included(hour_start), Included(Hours::inclusive_max()));
70
71 for hour in self.fields.hours.ordinals().range(hour_range).cloned() {
72 let minute_start = query.minute_lower_bound();
73 if !self.fields.minutes.ordinals().contains(&minute_start) {
74 query.reset_minute();
75 }
76 let minute_range =
77 (Included(minute_start), Included(Minutes::inclusive_max()));
78
79 for minute in self.fields.minutes.ordinals().range(minute_range).cloned() {
80 let second_start = query.second_lower_bound();
81 if !self.fields.seconds.ordinals().contains(&second_start) {
82 query.reset_second();
83 }
84 let second_range =
85 (Included(second_start), Included(Seconds::inclusive_max()));
86
87 for second in
88 self.fields.seconds.ordinals().range(second_range).cloned()
89 {
90 let timezone = after.timezone();
91 let candidate = if let Some(candidate) = timezone
92 .ymd(year as i32, month, day_of_month)
93 .and_hms_opt(hour, minute, second)
94 {
95 candidate
96 } else {
97 continue;
98 };
99 if !self
100 .fields
101 .days_of_week
102 .ordinals()
103 .contains(&candidate.weekday().number_from_sunday())
104 {
105 continue 'day_loop;
106 }
107 return Some(candidate);
108 }
109 query.reset_minute();
110 } query.reset_hour();
112 } query.reset_day_of_month();
114 } query.reset_month();
116 } }
118
119 None
121 }
122
123 fn prev_from<Z>(&self, before: &DateTime<Z>) -> Option<DateTime<Z>>
124 where
125 Z: TimeZone,
126 {
127 let mut query = PrevFromQuery::from(before);
128 for year in self
129 .fields
130 .years
131 .ordinals()
132 .range((Unbounded, Included(query.year_upper_bound())))
133 .rev()
134 .cloned()
135 {
136 let month_start = query.month_upper_bound();
137
138 if !self.fields.months.ordinals().contains(&month_start) {
139 query.reset_month();
140 }
141 let month_range = (Included(Months::inclusive_min()), Included(month_start));
142
143 for month in self
144 .fields
145 .months
146 .ordinals()
147 .range(month_range)
148 .rev()
149 .cloned()
150 {
151 let day_of_month_end = query.day_of_month_upper_bound();
152 if !self
153 .fields
154 .days_of_month
155 .ordinals()
156 .contains(&day_of_month_end)
157 {
158 query.reset_day_of_month();
159 }
160
161 let day_of_month_end = days_in_month(month, year).min(day_of_month_end);
162
163 let day_of_month_range = (
164 Included(DaysOfMonth::inclusive_min()),
165 Included(day_of_month_end),
166 );
167
168 'day_loop: for day_of_month in self
169 .fields
170 .days_of_month
171 .ordinals()
172 .range(day_of_month_range)
173 .rev()
174 .cloned()
175 {
176 let hour_start = query.hour_upper_bound();
177 if !self.fields.hours.ordinals().contains(&hour_start) {
178 query.reset_hour();
179 }
180 let hour_range = (Included(Hours::inclusive_min()), Included(hour_start));
181
182 for hour in self
183 .fields
184 .hours
185 .ordinals()
186 .range(hour_range)
187 .rev()
188 .cloned()
189 {
190 let minute_start = query.minute_upper_bound();
191 if !self.fields.minutes.ordinals().contains(&minute_start) {
192 query.reset_minute();
193 }
194 let minute_range =
195 (Included(Minutes::inclusive_min()), Included(minute_start));
196
197 for minute in self
198 .fields
199 .minutes
200 .ordinals()
201 .range(minute_range)
202 .rev()
203 .cloned()
204 {
205 let second_start = query.second_upper_bound();
206 if !self.fields.seconds.ordinals().contains(&second_start) {
207 query.reset_second();
208 }
209 let second_range =
210 (Included(Seconds::inclusive_min()), Included(second_start));
211
212 for second in self
213 .fields
214 .seconds
215 .ordinals()
216 .range(second_range)
217 .rev()
218 .cloned()
219 {
220 let timezone = before.timezone();
221 let candidate = if let Some(candidate) = timezone
222 .ymd(year as i32, month, day_of_month)
223 .and_hms_opt(hour, minute, second)
224 {
225 candidate
226 } else {
227 continue;
228 };
229 if !self
230 .fields
231 .days_of_week
232 .ordinals()
233 .contains(&candidate.weekday().number_from_sunday())
234 {
235 continue 'day_loop;
236 }
237 return Some(candidate);
238 }
239 query.reset_minute();
240 } query.reset_hour();
242 } query.reset_day_of_month();
244 } query.reset_month();
246 } }
248
249 None
251 }
252
253 pub fn upcoming<Z>(&self, timezone: Z) -> ScheduleIterator<'_, Z>
256 where
257 Z: TimeZone,
258 {
259 self.after(&timezone.from_utc_datetime(&Utc::now().naive_utc()))
260 }
261
262 pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
264 where
265 Z: TimeZone,
266 {
267 ScheduleIterator::new(self, after)
268 }
269
270 pub fn upcoming_owned<Z>(self, timezone: Z) -> ScheduleIteratorOwned<Z>
273 where
274 Z: TimeZone,
275 {
276 self.into_schedule_iterator(timezone.from_utc_datetime(&Utc::now().naive_utc()))
277 }
278
279 pub fn into_schedule_iterator<Z>(self, after: DateTime<Z>) -> ScheduleIteratorOwned<Z>
281 where
282 Z: TimeZone,
283 {
284 ScheduleIteratorOwned::new(self, after)
285 }
286 pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
287 where
288 Z: TimeZone,
289 {
290 self.fields.years.includes(date_time.year() as Ordinal)
291 && self.fields.months.includes(date_time.month() as Ordinal)
292 && self
293 .fields
294 .days_of_week
295 .includes(date_time.weekday().number_from_sunday())
296 && self
297 .fields
298 .days_of_month
299 .includes(date_time.day() as Ordinal)
300 && self.fields.hours.includes(date_time.hour() as Ordinal)
301 && self.fields.minutes.includes(date_time.minute() as Ordinal)
302 && self.fields.seconds.includes(date_time.second() as Ordinal)
303 }
304
305 pub fn years(&self) -> &impl TimeUnitSpec {
308 &self.fields.years
309 }
310
311 pub fn months(&self) -> &impl TimeUnitSpec {
314 &self.fields.months
315 }
316
317 pub fn days_of_month(&self) -> &impl TimeUnitSpec {
320 &self.fields.days_of_month
321 }
322
323 pub fn days_of_week(&self) -> &impl TimeUnitSpec {
326 &self.fields.days_of_week
327 }
328
329 pub fn hours(&self) -> &impl TimeUnitSpec {
332 &self.fields.hours
333 }
334
335 pub fn minutes(&self) -> &impl TimeUnitSpec {
338 &self.fields.minutes
339 }
340
341 pub fn seconds(&self) -> &impl TimeUnitSpec {
344 &self.fields.seconds
345 }
346
347 pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
348 self.fields == other.fields
349 }
350}
351
352impl Display for Schedule {
353 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
354 write!(f, "{}", self.source)
355 }
356}
357
358impl PartialEq for Schedule {
359 fn eq(&self, other: &Schedule) -> bool {
360 self.source == other.source
361 }
362}
363
364#[derive(Clone, Debug, Hash, PartialEq, Eq)]
365pub struct ScheduleFields {
366 years: Years,
367 days_of_week: DaysOfWeek,
368 months: Months,
369 days_of_month: DaysOfMonth,
370 hours: Hours,
371 minutes: Minutes,
372 seconds: Seconds,
373}
374
375impl ScheduleFields {
376 pub(crate) fn new(
377 seconds: Seconds,
378 minutes: Minutes,
379 hours: Hours,
380 days_of_month: DaysOfMonth,
381 months: Months,
382 days_of_week: DaysOfWeek,
383 years: Years,
384 ) -> ScheduleFields {
385 ScheduleFields {
386 years,
387 days_of_week,
388 months,
389 days_of_month,
390 hours,
391 minutes,
392 seconds,
393 }
394 }
395}
396#[derive(Debug, Clone, Hash, PartialEq, Eq)]
398pub struct ScheduleIterator<'a, Z>
399where
400 Z: TimeZone,
401{
402 is_done: bool,
403 schedule: &'a Schedule,
404 previous_datetime: DateTime<Z>,
405}
406
407impl<'a, Z> ScheduleIterator<'a, Z>
410where
411 Z: TimeZone,
412{
413 fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> ScheduleIterator<'a, Z> {
414 ScheduleIterator {
415 is_done: false,
416 schedule,
417 previous_datetime: starting_datetime.clone(),
418 }
419 }
420}
421
422impl<'a, Z> Iterator for ScheduleIterator<'a, Z>
423where
424 Z: TimeZone,
425{
426 type Item = DateTime<Z>;
427
428 fn next(&mut self) -> Option<DateTime<Z>> {
429 if self.is_done {
430 return None;
431 }
432 if let Some(next_datetime) = self.schedule.next_after(&self.previous_datetime) {
433 self.previous_datetime = next_datetime.clone();
434 Some(next_datetime)
435 } else {
436 self.is_done = true;
437 None
438 }
439 }
440}
441
442#[derive(Debug, Clone, Hash, PartialEq, Eq)]
445pub struct ScheduleIteratorOwned<Z>
446where
447 Z: TimeZone,
448{
449 is_done: bool,
450 schedule: Schedule,
451 previous_datetime: DateTime<Z>,
452}
453
454impl<Z> ScheduleIteratorOwned<Z>
455where
456 Z: TimeZone,
457{
458 fn new(schedule: Schedule, starting_datetime: DateTime<Z>) -> ScheduleIteratorOwned<Z> {
459 ScheduleIteratorOwned {
460 is_done: false,
461 schedule,
462 previous_datetime: starting_datetime,
463 }
464 }
465
466 #[inline(always)]
468 pub fn refresh_previous_datetime(&mut self, timezone: Z) {
469 self.previous_datetime = timezone.from_utc_datetime(&Utc::now().naive_utc());
470 }
471}
472
473impl<Z> Iterator for ScheduleIteratorOwned<Z>
474where
475 Z: TimeZone,
476{
477 type Item = DateTime<Z>;
478
479 fn next(&mut self) -> Option<DateTime<Z>> {
480 if self.is_done {
481 return None;
482 }
483 if let Some(next_datetime) = self.schedule.next_after(&self.previous_datetime) {
484 self.previous_datetime = next_datetime.clone();
485 Some(next_datetime)
486 } else {
487 self.is_done = true;
488 None
489 }
490 }
491}
492
493impl<'a, Z> DoubleEndedIterator for ScheduleIterator<'a, Z>
494where
495 Z: TimeZone,
496{
497 fn next_back(&mut self) -> Option<Self::Item> {
498 if self.is_done {
499 return None;
500 }
501
502 if let Some(prev_datetime) = self.schedule.prev_from(&self.previous_datetime) {
503 self.previous_datetime = prev_datetime.clone();
504 Some(prev_datetime)
505 } else {
506 self.is_done = true;
507 None
508 }
509 }
510}
511
512fn is_leap_year(year: Ordinal) -> bool {
513 let by_four = year % 4 == 0;
514 let by_hundred = year % 100 == 0;
515 let by_four_hundred = year % 400 == 0;
516 by_four && ((!by_hundred) || by_four_hundred)
517}
518
519fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
520 let is_leap_year = is_leap_year(year);
521 match month {
522 9 | 4 | 6 | 11 => 30,
523 2 if is_leap_year => 29,
524 2 => 28,
525 _ => 31,
526 }
527}
528
529#[cfg(test)]
530mod test {
531 use super::*;
532 use std::str::FromStr;
533
534 #[test]
535 fn test_next_and_prev_from() {
536 let expression = "0 5,13,40-42 17 1 Jan *";
537 let schedule = Schedule::from_str(expression).unwrap();
538
539 let next = schedule.next_after(&Utc::now());
540 println!("NEXT AFTER for {} {:?}", expression, next);
541 assert!(next.is_some());
542
543 let next2 = schedule.next_after(&next.unwrap());
544 println!("NEXT2 AFTER for {} {:?}", expression, next2);
545 assert!(next2.is_some());
546
547 let prev = schedule.prev_from(&next2.unwrap());
548 println!("PREV FROM for {} {:?}", expression, prev);
549 assert!(prev.is_some());
550 assert_eq!(prev, next);
551 }
552
553 #[test]
554 fn test_prev_from() {
555 let expression = "0 5,13,40-42 17 1 Jan *";
556 let schedule = Schedule::from_str(expression).unwrap();
557 let prev = schedule.prev_from(&Utc::now());
558 println!("PREV FROM for {} {:?}", expression, prev);
559 assert!(prev.is_some());
560 }
561
562 #[test]
563 fn test_next_after() {
564 let expression = "0 5,13,40-42 17 1 Jan *";
565 let schedule = Schedule::from_str(expression).unwrap();
566 let next = schedule.next_after(&Utc::now());
567 println!("NEXT AFTER for {} {:?}", expression, next);
568 assert!(next.is_some());
569 }
570
571 #[test]
572 fn test_upcoming_utc() {
573 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
574 let schedule = Schedule::from_str(expression).unwrap();
575 let mut upcoming = schedule.upcoming(Utc);
576 let next1 = upcoming.next();
577 assert!(next1.is_some());
578 let next2 = upcoming.next();
579 assert!(next2.is_some());
580 let next3 = upcoming.next();
581 assert!(next3.is_some());
582 println!("Upcoming 1 for {} {:?}", expression, next1);
583 println!("Upcoming 2 for {} {:?}", expression, next2);
584 println!("Upcoming 3 for {} {:?}", expression, next3);
585 }
586
587 #[test]
588 fn test_upcoming_rev_utc() {
589 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
590 let schedule = Schedule::from_str(expression).unwrap();
591 let mut upcoming = schedule.upcoming(Utc).rev();
592 let prev1 = upcoming.next();
593 assert!(prev1.is_some());
594 let prev2 = upcoming.next();
595 assert!(prev2.is_some());
596 let prev3 = upcoming.next();
597 assert!(prev3.is_some());
598 println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
599 println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
600 println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
601 }
602
603 #[test]
604 fn test_upcoming_local() {
605 use chrono::Local;
606 let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
607 let schedule = Schedule::from_str(expression).unwrap();
608 let mut upcoming = schedule.upcoming(Local);
609 let next1 = upcoming.next();
610 assert!(next1.is_some());
611 let next2 = upcoming.next();
612 assert!(next2.is_some());
613 let next3 = upcoming.next();
614 assert!(next3.is_some());
615 println!("Upcoming 1 for {} {:?}", expression, next1);
616 println!("Upcoming 2 for {} {:?}", expression, next2);
617 println!("Upcoming 3 for {} {:?}", expression, next3);
618 }
619
620 #[test]
621 fn test_schedule_to_string() {
622 let expression = "* 1,2,3 * * * *";
623 let schedule: Schedule = Schedule::from_str(expression).unwrap();
624 let result = String::from(schedule);
625 assert_eq!(expression, result);
626 }
627
628 #[test]
629 fn test_display_schedule() {
630 use std::fmt::Write;
631 let expression = "@monthly";
632 let schedule = Schedule::from_str(expression).unwrap();
633 let mut result = String::new();
634 write!(result, "{}", schedule).unwrap();
635 assert_eq!(expression, result);
636 }
637
638 #[test]
639 fn test_valid_from_str() {
640 let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
641 schedule.unwrap();
642 }
643
644 #[test]
645 fn test_invalid_from_str() {
646 let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
647 assert!(schedule.is_err());
648 }
649
650 #[test]
651 fn test_no_panic_on_nonexistent_time_after() {
652 use chrono::offset::TimeZone;
653 use chrono_tz::Tz;
654
655 let schedule_tz: Tz = "Europe/London".parse().unwrap();
656 let dt = schedule_tz
657 .ymd(2019, 10, 27)
658 .and_hms(0, 3, 29)
659 .checked_add_signed(chrono::Duration::hours(1)) .unwrap();
661 let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
662 let next = schedule.after(&dt).next().unwrap();
663 assert!(next > dt); }
665
666 #[test]
667 fn test_no_panic_on_nonexistent_time_before() {
668 use chrono::offset::TimeZone;
669 use chrono_tz::Tz;
670
671 let schedule_tz: Tz = "Europe/London".parse().unwrap();
672 let dt = schedule_tz
673 .ymd(2019, 10, 27)
674 .and_hms(0, 3, 29)
675 .checked_add_signed(chrono::Duration::hours(1)) .unwrap();
677 let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
678 let prev = schedule.after(&dt).rev().next().unwrap();
679 assert!(prev < dt); }
681
682 #[test]
683 fn test_time_unit_spec_equality() {
684 let schedule_1 = Schedule::from_str("@weekly").unwrap();
685 let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
686 let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
687 let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
688 assert_ne!(schedule_1, schedule_2);
689 assert!(schedule_1.timeunitspec_eq(&schedule_2));
690 assert!(schedule_3.timeunitspec_eq(&schedule_4));
691 }
692}