1#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(not(feature = "std"))]
6extern crate alloc;
7
8mod describe;
9pub mod parse;
10
11use chrono::prelude::*;
12
13use core::cmp;
14use core::fmt::Debug;
15use core::str::FromStr;
16
17use self::parse::{CronExpr, ExprValue, OrsExpr};
18
19pub(crate) mod internal {
20 pub trait Sealed {}
21}
22
23fn days_in_month(date: Date<Utc>) -> u32 {
25 match date.month() {
26 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
27 4 | 6 | 9 | 11 => 30,
28 2 => {
29 let year = date.year();
30 if year % 4 != 0 {
31 28
32 } else if year % 100 != 0 {
33 29
34 } else if year % 400 != 0 {
35 28
36 } else {
37 29
38 }
39 }
40 _ => unreachable!(),
41 }
42}
43
44trait TimePattern {
45 type Expr;
47
48 fn compile(expr: Self::Expr) -> Self;
50
51 fn contains(&self, date: DateTime<Utc>) -> bool;
53}
54
55const DBG_BAD_PATTERN: &str = "Value mapped out of range of valid bit patterns";
56
57macro_rules! debug_assert_pattern {
58 ($pat:expr, $mask:expr) => {
59 debug_assert!(($pat & !($mask)) == 0, DBG_BAD_PATTERN)
60 };
61}
62
63#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
64enum DaysOfWeekKind {
65 Pattern,
67 Star,
69 Last,
71 Nth,
74}
75
76#[derive(Debug, PartialEq, Eq, Hash, Clone)]
78struct DaysOfWeek(DaysOfWeekKind, u8);
79impl TimePattern for DaysOfWeek {
80 type Expr = parse::DayOfWeekExpr;
81
82 #[inline]
83 fn compile(expr: Self::Expr) -> Self {
84 match expr {
85 parse::DayOfWeekExpr::All => Self(DaysOfWeekKind::Star, 0),
86 parse::DayOfWeekExpr::Last(day) => Self(DaysOfWeekKind::Last, u8::from(day)),
87 parse::DayOfWeekExpr::Nth(day, nth) => {
88 Self(DaysOfWeekKind::Nth, (u8::from(nth) << 3) | u8::from(day))
89 }
90 parse::DayOfWeekExpr::Many(exprs) => Self(
91 DaysOfWeekKind::Pattern,
92 exprs.into_iter().fold(0, Self::add_ors),
93 ),
94 }
95 }
96 #[inline]
97 fn contains(&self, dt: DateTime<Utc>) -> bool {
98 self.contains_date(dt.date())
99 }
100}
101impl DaysOfWeek {
102 const BITS: u8 = 8;
103 const DAY_BITS: u8 = 0b0111_1111;
104 const ONE_DAY_BITS: u8 = 0b0000_0111;
105 const UPPER_BIT_BOUND: u8 = Self::DAY_BITS.trailing_ones() as u8;
106
107 #[inline]
108 fn kind(&self) -> DaysOfWeekKind {
109 self.0
110 }
111
112 fn is_star(&self) -> bool {
113 matches!(self.kind(), DaysOfWeekKind::Star)
114 }
115
116 #[inline]
117 fn byte_to_weekday(value: u8) -> Weekday {
118 match value {
119 0 => Weekday::Sun,
120 1 => Weekday::Mon,
121 2 => Weekday::Tue,
122 3 => Weekday::Wed,
123 4 => Weekday::Thu,
124 5 => Weekday::Fri,
125 6 => Weekday::Sat,
126 _ => unreachable!(),
127 }
128 }
129
130 #[inline]
131 fn last(&self) -> Option<Weekday> {
132 if self.kind() == DaysOfWeekKind::Last {
133 Some(Self::byte_to_weekday(self.1))
134 } else {
135 None
136 }
137 }
138
139 #[inline]
140 fn nth(&self) -> Option<(u8, Weekday)> {
141 if let Self(DaysOfWeekKind::Nth, values) = *self {
142 let weekday = values & Self::ONE_DAY_BITS;
143 let nth = values >> 3;
144 Some((nth, Self::byte_to_weekday(weekday)))
145 } else {
146 None
147 }
148 }
149
150 #[inline]
151 fn contains_date(&self, d: Date<Utc>) -> bool {
152 match *self {
153 Self(DaysOfWeekKind::Pattern, pattern) => {
154 let mask = 1u8 << d.weekday().num_days_from_sunday();
155 pattern & mask != 0
156 }
157 Self(DaysOfWeekKind::Nth, bits) => {
158 let weekday = bits & Self::ONE_DAY_BITS;
159 let nth = bits >> 3;
160 let current_weekday = d.weekday().num_days_from_sunday() as u8;
161
162 weekday == current_weekday && (d.day0() / 7) + 1 == nth as u32
163 }
164 Self(DaysOfWeekKind::Last, weekday) => {
165 let current_weekday = d.weekday().num_days_from_sunday() as u8;
166 weekday == current_weekday && d.day() + 7 > days_in_month(d)
167 }
168 _ => true,
169 }
170 }
171
172 #[inline]
173 fn value_pattern<T>(value: T) -> u8
174 where
175 T: Into<u8>,
176 {
177 let pattern = 1 << value.into();
178
179 debug_assert_pattern!(pattern, Self::DAY_BITS);
180
181 pattern
182 }
183
184 #[inline]
185 fn add_ors(mut pattern: u8, expr: OrsExpr<parse::DayOfWeek>) -> u8 {
186 match expr.normalize() {
187 OrsExpr::One(one) => pattern |= Self::value_pattern(one),
188 OrsExpr::Range(start, end) => {
189 if start <= end {
190 let start = u8::from(start);
191 let end = u8::from(end);
192
193 let mut bits = Self::DAY_BITS;
205 bits = (bits >> start) << start;
218 if end < Self::UPPER_BIT_BOUND {
237 let end_shift = Self::BITS.wrapping_sub(end + 1);
239 bits = (bits << end_shift) >> end_shift;
240 }
241
242 debug_assert_pattern!(bits, Self::DAY_BITS);
243
244 pattern |= bits;
245 } else {
246 let mut top_bits = Self::DAY_BITS;
264 let start = u8::from(start) - 1;
277 top_bits = (top_bits >> start) << start;
278
279 let mut bottom_bits = Self::DAY_BITS;
284 let end = u8::from(end) + 1;
299 let shift = Self::BITS.wrapping_sub(end);
300 bottom_bits = (bottom_bits << shift) >> shift;
301
302 let bits = top_bits | bottom_bits;
303
304 debug_assert_pattern!(bits, Self::DAY_BITS);
305
306 pattern |= bits;
307 }
308 }
309 OrsExpr::Step { start, end, step } => {
310 let start = u8::from(start);
311 let end = u8::from(end);
312 if start <= end {
313 let range = (start..=end).step_by(u8::from(step) as usize);
314
315 for shift in range {
316 pattern |= Self::value_pattern(shift);
317 }
318 } else {
319 let back = start..=parse::DayOfWeek::MAX;
320 let front = parse::DayOfWeek::MIN..=end;
321 let range = back.chain(front).step_by(u8::from(step) as usize);
322
323 for shift in range {
324 pattern |= Self::value_pattern(shift);
325 }
326 }
327 }
328 }
329 pattern
330 }
331}
332
333#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
335struct Minutes(u64);
336impl TimePattern for Minutes {
337 type Expr = parse::Expr<parse::Minute>;
338
339 #[inline]
340 fn compile(expr: Self::Expr) -> Self {
341 match expr {
342 parse::Expr::All => Self(Self::ALL),
343 parse::Expr::Many(exprs) => exprs.into_iter().fold(Self(0), Self::add_ors),
344 }
345 }
346
347 #[inline]
349 fn contains(&self, date: DateTime<Utc>) -> bool {
350 let mask = 1u64 << date.minute();
351 self.0 & mask != 0
352 }
353}
354impl Minutes {
355 const BITS: u8 = 64;
356 const ALL: u64 = 0x0FFFFFFFFFFFFFFF;
357 const UPPER_BIT_BOUND: u8 = Self::ALL.trailing_ones() as u8;
358
359 #[inline]
360 fn value_pattern<T>(value: T) -> u64
361 where
362 T: Into<u8>,
363 {
364 let pattern = 1 << value.into();
365
366 debug_assert_pattern!(pattern, Self::ALL);
367
368 pattern
369 }
370
371 #[inline]
372 fn add_ors(mut self, expr: OrsExpr<parse::Minute>) -> Self {
373 match expr.normalize() {
374 OrsExpr::One(one) => self.0 |= Self::value_pattern(one),
375 OrsExpr::Range(start, end) => {
376 if start <= end {
377 let start = u8::from(start);
378 let end = u8::from(end);
379
380 let mut bits = Self::ALL;
382 bits = (bits >> start) << start;
383 if end < Self::UPPER_BIT_BOUND {
384 let end_shift = Self::BITS.wrapping_sub(end + 1);
385 bits = (bits << end_shift) >> end_shift;
386 }
387 debug_assert_pattern!(bits, Self::ALL);
388
389 self.0 |= bits;
390 } else {
391 let start = u8::from(start) - 1;
392 let end = u8::from(end) + 1;
393
394 let top_bits = (Self::ALL >> start) << start;
395
396 let bottom_shift = Self::BITS.wrapping_sub(end);
397 let bottom_bits = (Self::ALL << bottom_shift) >> bottom_shift;
398
399 let bits = top_bits | bottom_bits;
400
401 debug_assert_pattern!(bits, Self::ALL);
402
403 self.0 |= bits;
404 }
405 }
406 OrsExpr::Step { start, end, step } => {
407 let start = u8::from(start);
408 let end = u8::from(end);
409 if start <= end {
410 let range = (start..=end).step_by(u8::from(step) as usize);
411
412 for shift in range {
413 self.0 |= Self::value_pattern(shift);
414 }
415 } else {
416 let back = start..=parse::Minute::MAX;
417 let front = parse::Minute::MIN..=end;
418 let range = back.chain(front).step_by(u8::from(step) as usize);
419
420 for shift in range {
421 self.0 |= Self::value_pattern(shift);
422 }
423 }
424 }
425 }
426 self
427 }
428}
429
430#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
432struct Hours(u32);
433impl TimePattern for Hours {
434 type Expr = parse::Expr<parse::Hour>;
435
436 #[inline]
437 fn compile(expr: Self::Expr) -> Self {
438 match expr {
439 parse::Expr::All => Self(Self::ALL),
440 parse::Expr::Many(exprs) => exprs.into_iter().fold(Self(0), Self::add_ors),
441 }
442 }
443
444 #[inline]
446 fn contains(&self, dt: DateTime<Utc>) -> bool {
447 self.contains_hour(dt.time())
448 }
449}
450impl Hours {
451 const BITS: u8 = 32;
452 const ALL: u32 = 0x00FFFFFF;
453 const UPPER_BIT_BOUND: u8 = Self::ALL.trailing_ones() as u8;
454
455 #[inline]
456 fn contains_hour(&self, time: NaiveTime) -> bool {
457 let mask = 1u32 << time.hour();
458 self.0 & mask != 0
459 }
460
461 #[inline]
462 fn value_pattern<T>(value: T) -> u32
463 where
464 T: Into<u8>,
465 {
466 let pattern = 1 << value.into();
467
468 debug_assert_pattern!(pattern, Self::ALL);
469
470 pattern
471 }
472
473 #[inline]
474 fn add_ors(mut self, expr: OrsExpr<parse::Hour>) -> Self {
475 match expr.normalize() {
476 OrsExpr::One(one) => self.0 |= Self::value_pattern(one),
477 OrsExpr::Range(start, end) => {
478 if start <= end {
479 let start = u8::from(start);
480 let end = u8::from(end);
481
482 let mut bits = Self::ALL;
484 bits = (bits >> start) << start;
485 if end < Self::UPPER_BIT_BOUND {
486 let end_shift = Self::BITS.wrapping_sub(end + 1);
487 bits = (bits << end_shift) >> end_shift;
488 }
489 debug_assert_pattern!(bits, Self::ALL);
490
491 self.0 |= bits;
492 } else {
493 let start = u8::from(start) - 1;
494 let end = u8::from(end) + 1;
495
496 let top_bits = (Self::ALL >> start) << start;
497
498 let bottom_shift = Self::BITS.wrapping_sub(end);
499 let bottom_bits = (Self::ALL << bottom_shift) >> bottom_shift;
500
501 let bits = top_bits | bottom_bits;
502
503 debug_assert_pattern!(bits, Self::ALL);
504
505 self.0 |= bits;
506 }
507 }
508 OrsExpr::Step { start, end, step } => {
509 let start = u8::from(start);
510 let end = u8::from(end);
511 if start <= end {
512 let range = (start..=end).step_by(u8::from(step) as usize);
513
514 for shift in range {
515 self.0 |= Self::value_pattern(shift);
516 }
517 } else {
518 let back = start..=parse::Hour::MAX;
519 let front = parse::Hour::MIN..=end;
520 let range = back.chain(front).step_by(u8::from(step) as usize);
521
522 for shift in range {
523 self.0 |= Self::value_pattern(shift);
524 }
525 }
526 }
527 }
528 self
529 }
530}
531
532#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
533enum DaysOfMonthKind {
534 Pattern,
535 Star,
536 Last,
537 Weekday,
538 LastWeekday,
539}
540
541#[derive(Debug, PartialEq, Eq, Hash, Clone)]
543struct DaysOfMonth(DaysOfMonthKind, u32);
544impl TimePattern for DaysOfMonth {
545 type Expr = parse::DayOfMonthExpr;
546
547 fn compile(expr: Self::Expr) -> Self {
548 use parse::{DayOfMonthExpr, Last};
549 match expr {
550 DayOfMonthExpr::All => Self(DaysOfMonthKind::Star, 0),
551 DayOfMonthExpr::Last(Last::Day) => Self(DaysOfMonthKind::Last, 0),
552 DayOfMonthExpr::Last(Last::Weekday) => Self(DaysOfMonthKind::LastWeekday, 0),
553 DayOfMonthExpr::Last(Last::Offset(offset)) => {
554 Self(DaysOfMonthKind::Last, u8::from(offset) as u32)
555 }
556 DayOfMonthExpr::Last(Last::OffsetWeekday(offset)) => {
557 Self(DaysOfMonthKind::LastWeekday, u8::from(offset) as u32)
558 }
559 DayOfMonthExpr::ClosestWeekday(day) => {
560 Self(DaysOfMonthKind::Weekday, (u8::from(day) + 1) as u32)
561 }
562 DayOfMonthExpr::Many(exprs) => Self(
563 DaysOfMonthKind::Pattern,
564 exprs.into_iter().fold(0, Self::add_ors),
565 ),
566 }
567 }
568
569 #[inline]
570 fn contains(&self, dt: DateTime<Utc>) -> bool {
571 self.contains_date(dt.date())
572 }
573}
574impl DaysOfMonth {
575 const BITS: u8 = 32;
576 const DAY_BITS: u32 = 0x0_7F_FF_FF_FF;
577 const ONE_DAY_BITS: u32 = 0b0001_1111;
578 const UPPER_BIT_BOUND: u8 = Self::DAY_BITS.trailing_ones() as u8;
579
580 #[inline]
581 fn kind(&self) -> DaysOfMonthKind {
582 self.0
583 }
584
585 #[inline]
586 fn is_last(&self) -> bool {
587 matches!(
588 self.kind(),
589 DaysOfMonthKind::Last | DaysOfMonthKind::LastWeekday
590 )
591 }
592
593 fn is_star(&self) -> bool {
594 matches!(self.kind(), DaysOfMonthKind::Star)
595 }
596
597 #[inline]
600 fn one_value(&self) -> u8 {
601 (self.1 & Self::ONE_DAY_BITS) as u8
602 }
603
604 #[inline]
605 fn first_set0(&self) -> Option<u8> {
606 let trailing = self.1.trailing_zeros() as u8;
607 if trailing < Self::BITS {
608 Some(trailing)
609 } else {
610 None
611 }
612 }
613
614 #[inline]
615 fn first_set(&self) -> Option<u8> {
616 self.first_set0().map(|i| i + 1)
617 }
618
619 #[inline]
620 fn contains_date(&self, date: Date<Utc>) -> bool {
621 let is_weekend = |weekday| matches!(weekday, Weekday::Sat | Weekday::Sun);
622 let is_weekday = |weekday| !is_weekend(weekday);
623
624 let days_in_month = days_in_month(date);
625 let day = date.day();
626
627 match self {
628 Self(DaysOfMonthKind::Pattern, pattern) => {
629 let mask = 1u32 << (day - 1);
630 pattern & mask != 0
631 }
632 Self(DaysOfMonthKind::Last, 0) => {
633 day == days_in_month
635 }
636 &Self(DaysOfMonthKind::Last, offset) => {
637 day + offset == days_in_month
643 }
644 Self(DaysOfMonthKind::LastWeekday, 0) => {
645 let weekday = date.weekday();
647 (is_weekday(weekday) && day == days_in_month)
648 || (weekday == Weekday::Fri && days_in_month - day < 3)
649 }
650 &Self(DaysOfMonthKind::LastWeekday, offset) => {
651 let weekday = date.weekday();
654 let day_offsetted = day + offset;
655 (is_weekday(weekday) && day_offsetted == days_in_month)
656 || (weekday == Weekday::Mon && day_offsetted - days_in_month < 3)
661 || (weekday == Weekday::Fri && day_offsetted + 1 == days_in_month)
662 }
663 &Self(DaysOfMonthKind::Weekday, expected_day) => {
664 let weekday = date.weekday();
665 (is_weekday(weekday) && day == expected_day)
666 || (weekday == Weekday::Mon && day - 1 == expected_day)
667 || (weekday == Weekday::Mon && day == 3 && expected_day == 1)
669 || (weekday == Weekday::Fri && day + 1 == expected_day)
670 || (weekday == Weekday::Fri && day + 2 == expected_day && expected_day == days_in_month)
672 }
673 _ => true,
674 }
675 }
676
677 #[inline]
678 fn value_pattern<T>(value: T) -> u32
679 where
680 T: Into<u8>,
681 {
682 let pattern = 1 << value.into();
683
684 debug_assert_pattern!(pattern, Self::DAY_BITS);
685
686 pattern
687 }
688
689 #[inline]
690 fn add_ors(mut pattern: u32, expr: OrsExpr<parse::DayOfMonth>) -> u32 {
691 match expr.normalize() {
692 OrsExpr::One(day) => pattern |= Self::value_pattern(day),
693 OrsExpr::Range(start, end) => {
694 if start <= end {
695 let start = u8::from(start);
696 let end = u8::from(end);
697
698 let mut bits = Self::DAY_BITS;
700 bits = (bits >> start) << start;
701 if end < Self::UPPER_BIT_BOUND {
702 let end_shift = Self::BITS.wrapping_sub(end + 1);
703 bits = (bits << end_shift) >> end_shift;
704 }
705 debug_assert_pattern!(bits, Self::DAY_BITS);
706
707 pattern |= bits;
708 } else {
709 let start = u8::from(start) - 1;
710 let end = u8::from(end) + 1;
711
712 let top_bits = (Self::DAY_BITS >> start) << start;
713
714 let bottom_shift = Self::BITS.wrapping_sub(end);
715 let bottom_bits = (Self::DAY_BITS << bottom_shift) >> bottom_shift;
716
717 let bits = top_bits | bottom_bits;
718
719 debug_assert_pattern!(bits, Self::DAY_BITS);
720
721 pattern |= bits;
722 }
723 }
724 OrsExpr::Step { start, end, step } => {
725 let start = u8::from(start);
726 let end = u8::from(end);
727 if start <= end {
728 let range = (start..=end).step_by(u8::from(step) as usize);
729
730 for shift in range {
731 pattern |= Self::value_pattern(shift);
732 }
733 } else {
734 let back = start..=parse::DayOfMonth::MAX;
735 let front = parse::DayOfMonth::MIN..=end;
736 let range = back.chain(front).step_by(u8::from(step) as usize);
737
738 for shift in range {
739 pattern |= Self::value_pattern(shift);
740 }
741 }
742 }
743 }
744 pattern
745 }
746}
747
748#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
750struct Months(u16);
751impl TimePattern for Months {
752 type Expr = parse::Expr<parse::Month>;
753
754 #[inline]
755 fn compile(expr: Self::Expr) -> Self {
756 match expr {
757 parse::Expr::All => Self(Self::ALL),
758 parse::Expr::Many(exprs) => exprs.into_iter().fold(Self(0), Self::add_ors),
759 }
760 }
761
762 #[inline]
764 fn contains(&self, date: DateTime<Utc>) -> bool {
765 self.contains_month(date.date())
766 }
767}
768impl Months {
769 const BITS: u8 = 16;
770 const ALL: u16 = 0x0FFF;
771 const UPPER_BIT_BOUND: u8 = Self::ALL.trailing_ones() as u8;
772
773 #[inline]
774 fn contains_month(&self, date: Date<Utc>) -> bool {
775 let mask = 1u16 << date.month0();
776 self.0 & mask != 0
777 }
778
779 #[inline]
780 fn value_pattern<T>(value: T) -> u16
781 where
782 T: Into<u8>,
783 {
784 let pattern = 1 << value.into();
785
786 debug_assert_pattern!(pattern, Self::ALL);
787
788 pattern
789 }
790
791 #[inline]
792 fn add_ors(mut self, expr: OrsExpr<parse::Month>) -> Self {
793 match expr.normalize() {
794 OrsExpr::One(one) => self.0 |= Self::value_pattern(one),
795 OrsExpr::Range(start, end) => {
796 if start <= end {
797 let start = u8::from(start);
798 let end = u8::from(end);
799
800 let mut bits = Self::ALL;
802 bits = (bits >> start) << start;
803 if end < Self::UPPER_BIT_BOUND {
804 let end_shift = Self::BITS.wrapping_sub(end + 1);
805 bits = (bits << end_shift) >> end_shift;
806 }
807 debug_assert_pattern!(bits, Self::ALL);
808
809 self.0 |= bits;
810 } else {
811 let start = u8::from(start) - 1;
812 let end = u8::from(end) + 1;
813
814 let top_bits = (Self::ALL >> start) << start;
815
816 let bottom_shift = Self::BITS.wrapping_sub(end);
817 let bottom_bits = (Self::ALL << bottom_shift) >> bottom_shift;
818
819 let bits = top_bits | bottom_bits;
820
821 debug_assert_pattern!(bits, Self::ALL);
822
823 self.0 |= bits;
824 }
825 }
826 OrsExpr::Step { start, end, step } => {
827 let start = u8::from(start);
828 let end = u8::from(end);
829 if start <= end {
830 let range = (start..=end).step_by(u8::from(step) as usize);
831
832 for shift in range {
833 self.0 |= Self::value_pattern(shift);
834 }
835 } else {
836 let back = start..=parse::Month::MAX;
837 let front = parse::Month::MIN..=end;
838 let range = back.chain(front).step_by(u8::from(step) as usize);
839
840 for shift in range {
841 self.0 |= Self::value_pattern(shift);
842 }
843 }
844 }
845 }
846 self
847 }
848}
849
850#[derive(Debug, PartialEq, Eq, Hash, Clone)]
876pub struct Cron {
877 minutes: Minutes,
878 hours: Hours,
879 dom: DaysOfMonth,
880 months: Months,
881 dow: DaysOfWeek,
882}
883
884impl FromStr for Cron {
885 type Err = parse::CronParseError;
886
887 fn from_str(s: &str) -> Result<Self, Self::Err> {
888 s.parse().map(Cron::new)
895 }
896}
897
898impl Cron {
899 pub fn new(expr: CronExpr) -> Self {
901 Self {
902 minutes: TimePattern::compile(expr.minutes),
903 hours: TimePattern::compile(expr.hours),
904 dom: TimePattern::compile(expr.doms),
905 months: TimePattern::compile(expr.months),
906 dow: TimePattern::compile(expr.dows),
907 }
908 }
909
910 #[inline]
927 pub fn any(&self) -> bool {
928 if self.dow.is_star() {
929 if self.dom.is_star() {
930 return true;
931 }
932
933 let first_set = if self.dom.is_last() {
934 match self.dom.one_value() {
935 0 => return true,
936 offset => offset + 1,
937 }
938 } else {
939 self.dom
940 .first_set()
941 .expect("At least one day should be set")
942 };
943
944 const MAX_31_MONTHS: u16 = 0b1010_1101_0101;
945 const MAX_30_MONTHS: u16 = 0b0101_0010_1000;
946 let max = if (self.months.0 & MAX_31_MONTHS) != 0 {
947 31
948 } else if (self.months.0 & MAX_30_MONTHS) != 0 {
949 30
950 } else {
951 29
952 };
953
954 first_set <= max
955 } else {
956 true
957 }
958 }
959
960 #[inline]
972 pub fn contains(&self, dt: DateTime<Utc>) -> bool {
973 let contains_minutes_hour_months =
974 self.minutes.contains(dt) && self.hours.contains(dt) && self.months.contains(dt);
975
976 if !contains_minutes_hour_months {
977 return false;
978 }
979
980 match (self.dom.is_star(), self.dow.is_star()) {
981 (true, true) => true,
982 (true, false) => self.dow.contains(dt),
983 (false, true) => self.dom.contains(dt),
984 (false, false) => self.dow.contains(dt) || self.dom.contains(dt),
985 }
986 }
987
988 #[inline]
989 fn contains_date(&self, date: Date<Utc>) -> bool {
990 if !self.months.contains_month(date) {
991 return false;
992 }
993
994 match (self.dom.is_star(), self.dow.is_star()) {
995 (true, true) => true,
996 (true, false) => self.dow.contains_date(date),
997 (false, true) => self.dom.contains_date(date),
998 (false, false) => self.dow.contains_date(date) || self.dom.contains_date(date),
999 }
1000 }
1001
1002 #[inline]
1021 pub fn iter_from(self, start: DateTime<Utc>) -> CronTimesIter {
1022 let start = start.trunc_subsecs(0).with_second(0).unwrap();
1023 let current = if self.any() {
1024 IterTime::Start(start)
1025 } else {
1026 IterTime::End
1027 };
1028
1029 CronTimesIter {
1030 cron: self,
1031 current,
1032 }
1033 }
1034
1035 #[inline]
1054 pub fn iter_after(self, start: DateTime<Utc>) -> CronTimesIter {
1055 let start = start.trunc_subsecs(0).with_second(0).unwrap();
1056 let current = if self.any() {
1057 IterTime::Next(start)
1058 } else {
1059 IterTime::End
1060 };
1061
1062 CronTimesIter {
1063 cron: self,
1064 current,
1065 }
1066 }
1067
1068 #[inline]
1081 pub fn next_from(&self, date: DateTime<Utc>) -> Option<DateTime<Utc>> {
1082 let date = date.trunc_subsecs(0).with_second(0).unwrap();
1083 if !self.any() {
1084 return None;
1085 }
1086
1087 if self.contains(date) {
1088 Some(date)
1089 } else {
1090 self.find_next(date)
1091 }
1092 }
1093
1094 #[inline]
1106 pub fn next_after(&self, date: DateTime<Utc>) -> Option<DateTime<Utc>> {
1107 let date = date.trunc_subsecs(0).with_second(0).unwrap();
1108 if self.any() {
1109 self.find_next(date)
1110 } else {
1111 None
1112 }
1113 }
1114
1115 const MUST_CONTAIN_MINUTE: &'static str = "Expression must contain at least one minute";
1116
1117 fn next_minute(time: NaiveTime) -> Option<NaiveTime> {
1118 if time.minute() < 59 {
1119 Some(NaiveTime::from_hms(time.hour(), time.minute() + 1, 0))
1120 } else if time.hour() < 23 {
1121 Some(NaiveTime::from_hms(time.hour() + 1, 0, 0))
1122 } else {
1123 None
1124 }
1125 }
1126
1127 fn find_next(&self, dt: DateTime<Utc>) -> Option<DateTime<Utc>> {
1128 if self.contains_date(dt.date()) {
1129 if let Some(next_minute) = Self::next_minute(dt.time()) {
1130 if let Some(time) = self.find_next_time(next_minute) {
1131 return dt.date().and_time(time);
1132 }
1133 }
1134 }
1135
1136 let tomorrow = dt.date().succ_opt()?;
1137 let time = NaiveTime::from_hms(0, 0, 0);
1138
1139 if let Some(next_date) = self.find_next_date(tomorrow) {
1140 let time = self.find_next_time(time).expect(Self::MUST_CONTAIN_MINUTE);
1141
1142 return next_date.and_time(time);
1143 }
1144
1145 let mut year = tomorrow.year().checked_add(1)?;
1146 loop {
1147 let next_year = Utc.ymd_opt(year, 1, 1).single()?;
1148 if let Some(next_date) = self.find_next_date(next_year) {
1149 let time = self.find_next_time(time).expect(Self::MUST_CONTAIN_MINUTE);
1150
1151 return next_date.and_time(time);
1152 } else {
1153 year = year.checked_add(1)?;
1154 }
1155 }
1156 }
1157
1158 fn find_next_minute(&self, current_minute: u32) -> Option<u32> {
1161 let Minutes(map) = self.minutes;
1162 let bottom_cleared = (map >> current_minute) << current_minute;
1164 let trailing_zeroes = bottom_cleared.trailing_zeros();
1167 if trailing_zeroes < Minutes::BITS as u32 {
1168 Some(trailing_zeroes)
1169 } else {
1170 None
1171 }
1172 }
1173
1174 fn find_next_hour(&self, current_hour: u32) -> Option<u32> {
1177 let Hours(map) = self.hours;
1178 let bottom_cleared = (map >> current_hour) << current_hour;
1179 let trailing_zeroes = bottom_cleared.trailing_zeros();
1180 if trailing_zeroes < Hours::BITS as u32 {
1181 Some(trailing_zeroes)
1182 } else {
1183 None
1184 }
1185 }
1186
1187 fn find_next_day(&self, date: Date<Utc>) -> Option<u32> {
1190 match (self.dom.is_star(), self.dow.is_star()) {
1191 (true, true) => Some(date.day0()),
1192 (true, false) => self.find_next_weekday(date),
1193 (false, true) => self.find_next_day_of_month(date),
1194 (false, false) => {
1195 let next_weekday = self.find_next_weekday(date);
1196 let next_day = self.find_next_day_of_month(date);
1197 match (next_day, next_weekday) {
1198 (Some(day), Some(weekday)) => Some(cmp::min(day, weekday)),
1199 (Some(day), None) => Some(day),
1200 (None, Some(day)) => Some(day),
1201 (None, None) => None,
1202 }
1203 }
1204 }
1205 }
1206
1207 fn find_next_day_of_month(&self, date: Date<Utc>) -> Option<u32> {
1210 let days_in_month = days_in_month(date);
1211 match self.dom.kind() {
1212 DaysOfMonthKind::Last => match self.dom.one_value() {
1213 0 => Some(days_in_month - 1),
1215 offset => {
1217 let expected = (days_in_month - 1).checked_sub(offset as u32)?;
1218 if expected < date.day0() {
1219 None
1220 } else {
1221 Some(expected)
1222 }
1223 }
1224 },
1225 DaysOfMonthKind::LastWeekday => match self.dom.one_value() {
1226 0 => {
1228 let days_in_month = days_in_month - 1;
1229 let real_day = match Self::weekday_for_day(days_in_month, date) {
1230 Weekday::Sat => days_in_month - 1,
1231 Weekday::Sun => days_in_month - 2,
1232 _ => days_in_month,
1233 };
1234 if real_day < date.day0() {
1235 None
1236 } else {
1237 Some(real_day)
1238 }
1239 }
1240 offset => {
1242 let days_in_month = days_in_month - 1;
1243 let last_in_month =
1244 days_in_month.checked_sub(offset as u32).map(|new_day| {
1245 match Self::weekday_for_day(new_day, date) {
1246 Weekday::Sat => {
1247 if new_day == 0 {
1248 2
1249 } else {
1250 new_day - 1
1251 }
1252 }
1253 Weekday::Sun => new_day + 1,
1254 _ => new_day,
1255 }
1256 })?;
1257 if last_in_month < date.day0() {
1258 None
1259 } else {
1260 Some(last_in_month)
1261 }
1262 }
1263 },
1264 DaysOfMonthKind::Weekday => {
1265 let days_in_month = days_in_month - 1;
1266 let expected_day = (self.dom.one_value() - 1) as u32;
1267 let real_day = match Self::weekday_for_day(expected_day, date) {
1268 Weekday::Sat => {
1269 if expected_day == 0 {
1270 2
1271 } else {
1272 expected_day - 1
1273 }
1274 }
1275 Weekday::Sun => {
1276 if expected_day == days_in_month {
1277 days_in_month - 2
1278 } else {
1279 expected_day + 1
1280 }
1281 }
1282 _ => expected_day,
1283 };
1284
1285 if real_day >= date.day0() && real_day <= days_in_month {
1286 Some(real_day)
1287 } else {
1288 None
1289 }
1290 }
1291 _ => {
1292 let current_day = date.day0();
1293 let map = self.dom.1 & DaysOfMonth::DAY_BITS;
1294 let bottom_cleared = (map >> current_day) << current_day;
1295 let trailing_zeroes = bottom_cleared.trailing_zeros();
1296 if trailing_zeroes < days_in_month {
1297 Some(trailing_zeroes)
1298 } else {
1299 None
1300 }
1301 }
1302 }
1303 }
1304
1305 fn find_next_weekday(&self, date: Date<Utc>) -> Option<u32> {
1308 let days_in_month = days_in_month(date);
1309 match self.dow.kind() {
1310 DaysOfWeekKind::Last => {
1311 let cron_weekday = self.dow.last().unwrap().num_days_from_sunday();
1312 let current_weekday = date.weekday().num_days_from_sunday();
1313 let weekday_offset = if cron_weekday < current_weekday {
1316 7 - (current_weekday - cron_weekday)
1321 } else {
1322 cron_weekday - current_weekday
1327 };
1328 let first_week_day = (date.day0() + weekday_offset) % 7;
1333 let last_day = match (days_in_month, first_week_day) {
1335 (29, day @ 0)
1337 | (30, day @ 0)
1338 | (30, day @ 1)
1339 | (31, day @ 0)
1340 | (31, day @ 1)
1341 | (31, day @ 2) => day + (7 * 4),
1342 (_, day) => day + (7 * 3),
1343 };
1344
1345 if date.day0() <= last_day {
1346 Some(last_day)
1347 } else {
1348 None
1349 }
1350 }
1351 DaysOfWeekKind::Nth => {
1352 let (nth, day) = self.dow.nth().unwrap();
1353 let cron_weekday = day.num_days_from_sunday();
1354 let current_weekday = date.weekday().num_days_from_sunday();
1355 let weekday_offset = if cron_weekday < current_weekday {
1356 7 - (current_weekday - cron_weekday)
1357 } else {
1358 cron_weekday - current_weekday
1359 };
1360 let first_week_day = (date.day0() + weekday_offset) % 7;
1361 let nth_day = first_week_day + (7 * (nth - 1) as u32);
1362 if nth_day < days_in_month && nth_day >= date.day0() {
1363 Some(nth_day)
1364 } else {
1365 None
1366 }
1367 }
1368 DaysOfWeekKind::Pattern => {
1369 let current_weekday = date.weekday().num_days_from_sunday();
1370 let map = self.dow.1 & DaysOfWeek::DAY_BITS;
1371 let bottom_cleared = (map >> current_weekday) << current_weekday;
1372 let trailing_zeroes = bottom_cleared.trailing_zeros();
1373 let next_day = if trailing_zeroes < DaysOfWeek::BITS as u32 {
1374 date.day0() + (trailing_zeroes - current_weekday)
1375 } else {
1376 let next_week = map.trailing_zeros();
1377 let remaining_days = (6 - current_weekday) + 1;
1378 date.day0() + remaining_days + next_week
1379 };
1380 if next_day < days_in_month {
1381 Some(next_day)
1382 } else {
1383 None
1384 }
1385 }
1386 _ => Some(date.day0()),
1387 }
1388 }
1389
1390 fn weekday_for_day(day: u32, date: Date<Utc>) -> Weekday {
1392 let expected_first_day = (day % 7) as usize;
1393 let first_day_of_current_weekday = (date.day0() % 7) as usize;
1394 let current_weekday = date.weekday().num_days_from_sunday() as usize;
1395
1396 use Weekday::*;
1398 const TABLE: [[[Weekday; 7]; 7]; 7] = [
1399 [
1400 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1401 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1402 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1403 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1404 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1405 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1406 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1407 ],
1408 [
1409 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1410 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1411 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1412 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1413 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1414 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1415 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1416 ],
1417 [
1418 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1419 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1420 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1421 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1422 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1423 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1424 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1425 ],
1426 [
1427 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1428 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1429 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1430 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1431 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1432 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1433 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1434 ],
1435 [
1436 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1437 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1438 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1439 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1440 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1441 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1442 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1443 ],
1444 [
1445 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1446 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1447 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1448 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1449 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1450 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1451 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1452 ],
1453 [
1454 [Mon, Tue, Wed, Thu, Fri, Sat, Sun],
1455 [Tue, Wed, Thu, Fri, Sat, Sun, Mon],
1456 [Wed, Thu, Fri, Sat, Sun, Mon, Tue],
1457 [Thu, Fri, Sat, Sun, Mon, Tue, Wed],
1458 [Fri, Sat, Sun, Mon, Tue, Wed, Thu],
1459 [Sat, Sun, Mon, Tue, Wed, Thu, Fri],
1460 [Sun, Mon, Tue, Wed, Thu, Fri, Sat],
1461 ],
1462 ];
1463
1464 TABLE[first_day_of_current_weekday][expected_first_day][current_weekday]
1465 }
1466
1467 fn find_next_month(&self, current_month: u32) -> Option<u32> {
1470 let Months(map) = self.months;
1471 let bottom_cleared = (map >> current_month) << current_month;
1472 let trailing_zeroes = bottom_cleared.trailing_zeros();
1473 if trailing_zeroes < Months::BITS as u32 {
1474 Some(trailing_zeroes)
1475 } else {
1476 None
1477 }
1478 }
1479
1480 fn find_next_time(&self, time: NaiveTime) -> Option<NaiveTime> {
1481 let hour = time.hour();
1482 if self.hours.contains_hour(time) {
1483 if let Some(next_minute) = self.find_next_minute(time.minute()) {
1484 return Some(NaiveTime::from_hms(hour, next_minute, 0));
1485 }
1486 }
1487
1488 if hour < 23 {
1489 if let Some(next_hour) = self.find_next_hour(hour + 1) {
1490 let minute = self.find_next_minute(0).expect(Self::MUST_CONTAIN_MINUTE);
1491
1492 return Some(NaiveTime::from_hms(next_hour, minute, 0));
1493 }
1494 }
1495
1496 None
1497 }
1498
1499 fn find_next_date(&self, date: Date<Utc>) -> Option<Date<Utc>> {
1500 const VALID_NEXT_DAY: &str = "Day should be valid for giving month";
1501 const VALID_NEXT_MONTH: &str = "Month should be valid";
1502
1503 let mut month = date.month();
1504 if self.months.contains_month(date) {
1505 if let Some(next_day) = self.find_next_day(date) {
1506 return Some(date.with_day0(next_day).expect(VALID_NEXT_DAY));
1507 }
1508 }
1509
1510 while month <= 11 {
1511 if let Some(next_month) = self.find_next_month(month) {
1512 month = next_month;
1513
1514 let start_month_date = date
1515 .with_day(1)
1516 .expect(VALID_NEXT_DAY)
1517 .with_month0(month)
1518 .expect(VALID_NEXT_MONTH);
1519 if let Some(month_day) = self.find_next_day(start_month_date) {
1520 return Some(start_month_date.with_day0(month_day).expect(VALID_NEXT_DAY));
1521 } else {
1522 month += 1;
1523 }
1524 } else {
1525 break;
1526 }
1527 }
1528
1529 None
1530 }
1531}
1532
1533enum IterTime {
1534 Start(DateTime<Utc>),
1536 Next(DateTime<Utc>),
1538 End,
1540}
1541
1542pub struct CronTimesIter {
1548 cron: Cron,
1549 current: IterTime,
1550}
1551
1552impl CronTimesIter {
1553 pub fn cron(&self) -> &Cron {
1555 &self.cron
1556 }
1557}
1558
1559impl Iterator for CronTimesIter {
1560 type Item = DateTime<Utc>;
1561
1562 fn next(&mut self) -> Option<Self::Item> {
1563 match self.current {
1564 IterTime::Start(time) => {
1565 self.current = IterTime::Next(time);
1566 if self.cron.contains(time) {
1567 Some(time)
1568 } else {
1569 self.next() }
1571 }
1572 IterTime::Next(time) => match self.cron.find_next(time) {
1573 Some(date) => {
1574 self.current = IterTime::Next(date);
1575 Some(date)
1576 }
1577 None => {
1578 self.current = IterTime::End;
1579 None
1580 }
1581 },
1582 IterTime::End => None,
1583 }
1584 }
1585}
1586
1587impl core::iter::FusedIterator for CronTimesIter {}
1588
1589#[cfg(test)]
1590mod tests {
1591 use super::*;
1592
1593 fn check_does_contain(cron: &str, dates: impl IntoIterator<Item = impl AsRef<str>>) {
1594 let parsed: Cron = cron.parse().unwrap();
1595
1596 for date in dates
1597 .into_iter()
1598 .map(|s| s.as_ref().parse::<DateTime<Utc>>().unwrap())
1599 {
1600 assert!(
1601 parsed.contains(date),
1602 "Cron \"{}\" should contain {}. Compiled: {:#?}",
1603 cron,
1604 date,
1605 parsed
1606 );
1607 }
1608 }
1609
1610 fn check_does_not_contain(cron: &str, dates: impl IntoIterator<Item = impl AsRef<str>>) {
1611 let parsed: Cron = cron.parse().unwrap();
1612
1613 for date in dates
1614 .into_iter()
1615 .map(|s| s.as_ref().parse::<DateTime<Utc>>().unwrap())
1616 {
1617 assert!(
1618 !parsed.contains(date),
1619 "Cron \"{}\" shouldn't contain {}. Compiled {:#?}",
1620 cron,
1621 date,
1622 parsed
1623 );
1624 }
1625 }
1626
1627 #[test]
1628 fn parse_check_anytime() {
1629 check_does_contain(
1630 "* * * * *",
1631 &[
1632 "1970-01-1T00:00:00+00:00",
1633 "2016-11-08T23:53:57+00:00",
1634 "2020-07-04T15:42:30+00:00",
1635 "2072-02-29T01:15:23+00:00",
1636 ],
1637 );
1638 }
1639
1640 #[test]
1641 fn parse_check_specific_time() {
1642 let cron = "5 0 23 8 *";
1643
1644 check_does_contain(
1645 cron,
1646 &["2020-08-23T00:05:00+00:00", "2020-08-23T00:05:30+00:00"],
1647 );
1648
1649 check_does_not_contain(
1650 cron,
1651 &[
1652 "1970-01-1T00:00:00+00:00",
1653 "2016-11-08T23:53:57+00:00",
1654 "2020-07-04T15:42:30+00:00",
1655 "2072-02-29T01:15:23+00:00",
1656 "2020-08-23T11:05:00+00:00",
1657 ],
1658 );
1659 }
1660
1661 #[test]
1663 fn parse_check_specific_time_as_ranges() {
1664 let cron = "5-5 0-0 23-23 8-8 *";
1665
1666 check_does_contain(
1667 cron,
1668 &["2020-08-23T00:05:00+00:00", "2020-08-23T00:05:30+00:00"],
1669 );
1670
1671 check_does_not_contain(
1672 cron,
1673 &[
1674 "1970-01-01T00:00:00+00:00",
1675 "2016-11-08T23:53:57+00:00",
1676 "2020-07-04T15:42:30+00:00",
1677 "2072-02-29T01:15:23+00:00",
1678 "2020-08-23T11:05:00+00:00",
1679 ],
1680 );
1681 }
1682
1683 #[test]
1684 fn parse_check_overflow_time_ranges() {
1685 let cron = "59-0 23-0 31-1 12-1 *";
1688
1689 check_does_contain(
1690 cron,
1691 &[
1692 "2020-01-31T00:59:00+00:00",
1693 "2020-01-31T00:00:00+00:00",
1694 "2020-01-31T23:59:00+00:00",
1695 "2020-01-31T23:00:00+00:00",
1696 "2020-01-01T00:59:00+00:00",
1697 "2020-01-01T00:00:00+00:00",
1698 "2020-01-01T23:59:00+00:00",
1699 "2020-01-01T23:00:00+00:00",
1700 "2020-12-31T00:59:00+00:00",
1701 "2020-12-31T00:00:00+00:00",
1702 "2020-12-31T23:59:00+00:00",
1703 "2020-12-31T23:00:00+00:00",
1704 "2020-12-01T00:59:00+00:00",
1705 "2020-12-01T00:00:00+00:00",
1706 "2020-12-01T23:59:00+00:00",
1707 "2020-12-01T23:00:00+00:00",
1708 ],
1709 );
1710
1711 let cron = "0 0 * JAN SAT-SUN";
1713
1714 check_does_contain(
1715 cron,
1716 &[
1717 "2020-01-04T00:00:00+00:00",
1718 "2020-01-05T00:00:00+00:00",
1719 "2020-01-11T00:00:00+00:00",
1720 "2020-01-12T00:00:00+00:00",
1721 ],
1722 );
1723 }
1724
1725 #[test]
1726 fn parse_check_limits() {
1727 let cron = "0,59 0,23 1,31 1,12 *";
1728
1729 check_does_contain(
1730 cron,
1731 &[
1732 "2020-01-01T00:00:00+00:00",
1733 "2020-01-01T00:59:00+00:00",
1734 "2020-01-01T23:59:00+00:00",
1735 "2020-01-31T23:59:00+00:00",
1736 "2020-12-31T23:59:00+00:00",
1737 ],
1738 );
1739 }
1740
1741 #[test]
1742 fn parse_check_anytime_but_its_ranges() {
1743 let cron = "0-59 0-23 1-31 1-12 *";
1744
1745 check_does_contain(
1746 cron,
1747 &[
1748 "1970-01-1T00:00:00+00:00",
1749 "2016-11-08T23:53:57+00:00",
1750 "2020-07-04T15:42:30+00:00",
1751 "2072-02-29T01:15:23+00:00",
1752 ],
1753 );
1754
1755 let cron = "0-59 0-23 * 1-12 1-7";
1756
1757 check_does_contain(
1758 cron,
1759 &[
1760 "1970-01-1T00:00:00+00:00",
1761 "2016-11-08T23:53:57+00:00",
1762 "2020-07-04T15:42:30+00:00",
1763 "2072-02-29T01:15:23+00:00",
1764 ],
1765 );
1766 }
1767
1768 #[test]
1769 fn parse_check_leap_days() {
1770 let cron = "0 0 L FEB *";
1771
1772 check_does_contain(
1773 cron,
1774 &[
1775 "2400-02-29T00:00:00+00:00",
1776 "2300-02-28T00:00:00+00:00",
1777 "2200-02-28T00:00:00+00:00",
1778 "2100-02-28T00:00:00+00:00",
1779 "2024-02-29T00:00:00+00:00",
1780 "2020-02-29T00:00:00+00:00",
1781 "2004-02-29T00:00:00+00:00",
1782 "2000-02-29T00:00:00+00:00",
1783 ],
1784 );
1785 }
1786
1787 #[test]
1788 fn parse_check_offset_leap_days() {
1789 let cron = "0 0 L-1 FEB *";
1790
1791 check_does_contain(
1792 cron,
1793 &[
1794 "2400-02-28T00:00:00+00:00",
1795 "2300-02-27T00:00:00+00:00",
1796 "2200-02-27T00:00:00+00:00",
1797 "2100-02-27T00:00:00+00:00",
1798 "2024-02-28T00:00:00+00:00",
1799 "2020-02-28T00:00:00+00:00",
1800 "2004-02-28T00:00:00+00:00",
1801 "2000-02-28T00:00:00+00:00",
1802 ],
1803 );
1804
1805 check_does_not_contain(
1806 cron,
1807 &[
1808 "2400-02-29T00:00:00+00:00",
1809 "2300-02-28T00:00:00+00:00",
1810 "2200-02-28T00:00:00+00:00",
1811 "2100-02-28T00:00:00+00:00",
1812 "2024-02-29T00:00:00+00:00",
1813 "2020-02-29T00:00:00+00:00",
1814 "2004-02-29T00:00:00+00:00",
1815 "2000-02-29T00:00:00+00:00",
1816 ],
1817 );
1818 }
1819
1820 #[test]
1821 fn parse_check_offset_weekend_start_months() {
1822 let cron = "0 0 L-30W * *";
1823
1824 check_does_contain(
1825 cron,
1826 &["2021-05-3T00:00:00+00:00", "2022-01-3T00:00:00+00:00"],
1827 );
1828 }
1829
1830 #[test]
1831 fn parse_check_offset_weekend_start_months_beyond_days() {
1832 let cron = "0 0 L-28W FEB *";
1833
1834 check_does_not_contain(
1835 cron,
1836 &["2021-05-3T00:00:00+00:00", "2022-01-3T00:00:00+00:00"],
1837 );
1838 }
1839
1840 #[test]
1841 fn parse_check_last_weekdays() {
1842 let cron = "0 0 LW MAY *";
1843
1844 check_does_contain(
1845 cron,
1846 &[
1847 "2025-05-30T00:00:00+00:00", "2021-05-31T00:00:00+00:00", "2020-05-29T00:00:00+00:00", ],
1851 );
1852 }
1853
1854 #[test]
1855 fn parse_check_last_weekdays_offset() {
1856 let cron = "0 0 L-1W MAY *";
1857
1858 check_does_contain(
1859 cron,
1860 &[
1861 "2025-05-30T00:00:00+00:00", "2021-05-31T00:00:00+00:00", "2020-05-29T00:00:00+00:00", ],
1865 );
1866 }
1867
1868 #[test]
1869 fn parse_check_closest_weekday() {
1870 let cron = "0 0 1W MAY *";
1871
1872 check_does_contain(
1873 cron,
1874 &[
1875 "2020-05-01T00:00:00+00:00", "2022-05-02T00:00:00+00:00", "2021-05-03T00:00:00+00:00", ],
1879 )
1880 }
1881
1882 #[test]
1883 fn parse_check_last_weekday() {
1884 let cron = "0 0 * * 7L"; check_does_contain(
1887 cron,
1888 &[
1889 "2020-01-25T00:00:00+00:00",
1890 "2020-02-29T00:00:00+00:00",
1891 "2020-03-28T00:00:00+00:00",
1892 "2020-04-25T00:00:00+00:00",
1893 "2020-05-30T00:00:00+00:00",
1894 ],
1895 );
1896
1897 check_does_not_contain(
1898 cron,
1899 &[
1900 "2020-01-31T00:00:00+00:00",
1901 "2020-02-28T00:00:00+00:00",
1902 "2020-03-31T00:00:00+00:00",
1903 "2020-04-30T00:00:00+00:00",
1904 "2020-05-31T00:00:00+00:00",
1905 ],
1906 )
1907 }
1908
1909 #[test]
1910 fn parse_check_nth_weekday() {
1911 let cron = "0 0 * * SAT#5"; check_does_contain(
1914 cron,
1915 &[
1916 "2020-02-29T00:00:00+00:00",
1917 "2020-05-30T00:00:00+00:00",
1918 "2020-08-29T00:00:00+00:00",
1919 "2020-10-31T00:00:00+00:00",
1920 ],
1921 );
1922
1923 check_does_not_contain(
1924 cron,
1925 &[
1926 "2020-01-31T00:00:00+00:00",
1927 "2020-02-28T00:00:00+00:00",
1928 "2020-03-31T00:00:00+00:00",
1929 "2020-04-30T00:00:00+00:00",
1930 "2020-05-31T00:00:00+00:00",
1931 ],
1932 )
1933 }
1934
1935 #[test]
1936 fn parse_check_steps() {
1937 let cron = "*/15,30-59/10 0 * * *";
1939
1940 check_does_contain(
1941 cron,
1942 &[
1943 "2020-01-01T00:00:00+00:00",
1944 "2020-01-01T00:15:00+00:00",
1945 "2020-01-01T00:30:00+00:00",
1946 "2020-01-01T00:40:00+00:00",
1947 "2020-01-01T00:45:00+00:00",
1948 "2020-01-01T00:50:00+00:00",
1949 ],
1950 )
1951 }
1952
1953 #[test]
1954 fn parse_check_overflow_range_step() {
1955 let cron = "0 20-4/2 * * *";
1957
1958 check_does_contain(
1959 cron,
1960 &[
1961 "2020-01-01T20:00:00+00:00",
1962 "2020-01-01T22:00:00+00:00",
1963 "2020-01-01T00:00:00+00:00",
1964 "2020-01-01T02:00:00+00:00",
1965 "2020-01-01T04:00:00+00:00",
1966 ],
1967 );
1968 }
1969}