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