[go: up one dir, main page]

saffron/
lib.rs

1//! A "Quartz scheduler"-like cron parser powering Cron Triggers on Cloudflare Workers.
2
3#![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
23/// Returns the number of days in the month, 28-31
24fn 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    /// A parsed time expression value
46    type Expr;
47
48    /// Compiles the expression into its most compressed form.
49    fn compile(expr: Self::Expr) -> Self;
50
51    /// Checks if the pattern contains the given DateTime.
52    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    /// An expression over a set of values, ranges, or steps
66    Pattern,
67    /// A '*' expression
68    Star,
69    /// A 'L' expression for the last day. One day is paired with this making it easier to access
70    Last,
71    /// A '#' expression for an nth day of the month. One day and one nth value is paired making it
72    /// easier to access
73    Nth,
74}
75
76/// A bit-mask of all the days of the week set in a cron expression.
77#[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                    // example: MON-FRI (or 2-6) (true value: 1-5)
194                    // our bit map goes in reverse, so for weekdays
195                    // our final mask should look like this
196                    //
197                    // ... ALL SAT FRI THU WED TUE MON SUN
198                    // ... 0   0   1   1   1   1   1   0
199                    //
200                    // to start with, our mask looks like this
201                    //
202                    // ... ALL SAT FRI THU WED TUE MON SUN
203                    // ... 0   1   1   1   1   1   1   1
204                    let mut bits = Self::DAY_BITS;
205                    // remove the end bits by shifting the bits to the right
206                    // by the start value (1), then shift it back.
207                    //
208                    // shift right by 1
209                    //                                 truncated
210                    // ... ALL SAT FRI THU WED TUE MON SUN | (OOB)
211                    // ... 0   0   1   1   1   1   1   1   | 1
212                    //
213                    // shift left by 1
214                    //
215                    // ... ALL SAT FRI THU WED TUE MON SUN
216                    // ... 0   1   1   1   1   1   1   0
217                    bits = (bits >> start) << start;
218                    // remove the start bits in the same way, shift the bits
219                    // to the left by the number of bits in the integer (8) minus
220                    // the end value (5) minus 1 (8 - 5 - 1 = 2).
221                    // if we had a value that took up the whole bit map with a range
222                    // that reached the max value, this operation would result in -1.
223                    // In that case, we'd floor to 0 and not shift at all. but because
224                    // it's the max value, we don't actually need to shift to truncate at
225                    // all. so we can just skip this in that case.
226                    //
227                    // shift left by 2
228                    // truncated
229                    // (OOB)   | ALL SAT FRI THU WED TUE MON SUN
230                    // 0   1   | 1   1   1   1   1   0   0   0
231                    //
232                    // shift right by 2
233                    //
234                    // ... ALL SAT FRI THU WED TUE MON SUN
235                    // ... 0   0   1   1   1   1   1   0
236                    if end < Self::UPPER_BIT_BOUND {
237                        // this won't overflow, so we might as well use wrapping arithmetic anyway
238                        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                    // example : FRI-SUN (6-0)
247                    // to match up with quartz schedulers, we have to support wrapping
248                    // around, so for example with this expression, FRI,SAT,SUN,
249                    // which should look like this:
250                    //
251                    // ... ALL SAT FRI THU WED TUE MON SUN
252                    // ... 0   1   1   0   0   0   0   1
253                    //
254                    // we remove bits from the middle a bit differently
255                    // instead of like we do above. we have to make two
256                    // masks which are missing either the left or right side
257                    // and then OR those together.
258                    //
259                    // same as before, our first mask starts like this
260                    //
261                    // ... ALL SAT FRI THU WED TUE MON SUN
262                    // ... 0   1   1   1   1   1   1   1
263                    let mut top_bits = Self::DAY_BITS;
264                    // to remove the bottom bits, shift the top bits to the right
265                    // by the start value (6) minus one (5), then shift back.
266                    //
267                    // shift right by 5
268                    //                                 truncated
269                    // ... ALL SAT FRI THU WED TUE MON SUN | (OOB)
270                    // ... 0   0   0   0   0   0   1   1   | 1   ...
271                    //
272                    // shift left by 5
273                    //
274                    // ... ALL SAT FRI THU WED TUE MON SUN
275                    // ... 0   1   1   0   0   0   0   0
276                    let start = u8::from(start) - 1;
277                    top_bits = (top_bits >> start) << start;
278
279                    // make a separate mask
280                    //
281                    // ... ALL SAT FRI THU WED TUE MON SUN
282                    // ... 0   1   1   1   1   1   1   1
283                    let mut bottom_bits = Self::DAY_BITS;
284                    // to remove the top bits, shift the top bits to the left
285                    // by the number of bits in the integer (8) minus the end
286                    // value (0) plus one (8 - 0 + 1 = 7)
287                    //
288                    // shift left by 7
289                    // truncated
290                    // ... (OOB)  | Out of mask bounds  ...
291                    // ... 1   1  | 1   0   0   0   0   ...
292                    //
293                    //
294                    // shift right by 7
295                    //
296                    // ... ALL SAT FRI THU WED TUE MON SUN
297                    // ... 0   0   0   0   0   0   0   1
298                    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/// A bit-mask of all minutes in an hour set in a cron expression.
334#[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    /// Returns whether this mask contains the minute value 0-59
348    #[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                    // learn how this works in DayOfWeek's add_ors function
381                    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/// A bit-mask of all hours in a day set in a cron expression.
431#[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    /// Returns whether this mask contains the hour value 0-23
445    #[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                    // learn how this works in DayOfWeek's add_ors function
483                    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/// A bit-mask of all the days of the month set in a cron expression.
542#[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    /// Returns the one day set in this expression. Used to get last day offsets and the day
598    /// in a closest weekday expression
599    #[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                // 'L'
634                day == days_in_month
635            }
636            &Self(DaysOfMonthKind::Last, offset) => {
637                // 'L' with an offset
638                // Example: 'L-3'
639                // Add to the day instead of subtracting from the days in the month,
640                // since we allow an offset of 30, but the days in the month could be less
641                // resulting in underflow.
642                day + offset == days_in_month
643            }
644            Self(DaysOfMonthKind::LastWeekday, 0) => {
645                // 'LW'
646                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                // 'L' with an offset with the nearest weekday.
652                // Example: 'L-3W'
653                let weekday = date.weekday();
654                let day_offsetted = day + offset;
655                (is_weekday(weekday) && day_offsetted == days_in_month)
656                    // don't check for weekend month ends since we're always offset by one
657                    // at least, so our "end" can't be on a weekend ending month
658                    // but do check if the month starts with a weekend and this is that weekend's
659                    // Saturday or Sunday
660                    || (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                    // check for 1W on months where the 1st is Saturday and the 2nd is Sunday
668                    || (weekday == Weekday::Mon && day == 3 && expected_day == 1)
669                    || (weekday == Weekday::Fri && day + 1 == expected_day)
670                    // check for 31W, 30W, 29W, 28W where they're the last day of the month and are on Sunday
671                    || (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                    // learn how this works in DayOfWeek's add_ors function
699                    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/// A bit-mask of all the months set in a cron expression.
749#[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    /// Returns whether this mask contains the month value 0-11
763    #[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                    // learn how this works in DayOfWeek's add_ors function
801                    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/// A cron value. This can be used to iterate over all future matching times or quickly check if
851/// a given time matches.
852///
853/// # Example
854/// ```
855/// use saffron::Cron;
856/// use chrono::prelude::*;
857///
858/// let cron: Cron = "*/10 0 * OCT MON".parse().expect("Couldn't parse expression!");
859///
860/// // check if a given time is contained in an expression
861/// assert!(cron.contains(Utc.ymd(2020, 10, 19).and_hms(0, 30, 0)));
862///
863/// // iterate over all future matching times
864/// for time in cron.clone().iter_from(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)).take(5) {
865///     // Prints
866///     // 1970-10-05 00:00:00 UTC
867///     // 1970-10-05 00:10:00 UTC
868///     // 1970-10-05 00:20:00 UTC
869///     // 1970-10-05 00:30:00 UTC
870///     // 1970-10-05 00:40:00 UTC
871///     println!("{}", time);
872///     assert!(cron.contains(time));
873/// }
874/// ```
875#[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        // parse and compile
889        // Any parsed expression can have redundant info, but we can
890        // easily compress it into a neat bit map where each of the bits
891        // of an integer represent the minutes/hours/days/months/weekdays
892        // in a cron expression. It might be compressable further but I
893        // doubt we'll need to do that.
894        s.parse().map(Cron::new)
895    }
896}
897
898impl Cron {
899    /// Simplifies the cron expression into a cron value.
900    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    /// Returns whether this cron value will ever match any giving time.
911    ///
912    /// Some values can never match any given time. If an value matches
913    /// for a day of the month that's beyond any of the valid days of the months matched
914    /// then the value can never match.
915    ///
916    /// # Example
917    /// ```
918    /// use saffron::Cron;
919    ///
920    /// // Does have any since February has a 29th day on leap years
921    /// assert!("* * 29 2 *".parse::<Cron>().unwrap().any());
922    ///
923    /// // Does not have any since November does not have a 31st day
924    /// assert!(!"* * 31 11 *".parse::<Cron>().unwrap().any());
925    /// ```
926    #[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    /// Returns whether this cron value matches the given time.
961    /// # Example
962    /// ```
963    /// use saffron::Cron;
964    /// use chrono::prelude::*;
965    ///
966    /// let cron: Cron = "*/10 0 * OCT MON".parse().expect("Couldn't parse expression!");
967    ///
968    /// // check if a given time is contained in an expression
969    /// assert!(cron.contains(Utc.ymd(2020, 10, 19).and_hms(0, 30, 0)));
970    /// ```
971    #[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    /// Creates an iterator of date times that match with the cron value.
1003    ///
1004    /// # Example
1005    /// ```
1006    /// use saffron::Cron;
1007    /// use chrono::prelude::*;
1008    ///
1009    /// let cron = "*/10 * * * *".parse::<Cron>().expect("Couldn't parse expression!");
1010    /// for time in cron.iter_from(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)).take(5) {
1011    ///     // Prints
1012    ///     // 1970-01-01 00:00:00 UTC
1013    ///     // 1970-01-01 00:10:00 UTC
1014    ///     // 1970-01-01 00:20:00 UTC
1015    ///     // 1970-01-01 00:30:00 UTC
1016    ///     // 1970-01-01 00:40:00 UTC
1017    ///     println!("{}", time)
1018    /// }
1019    /// ```
1020    #[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    /// Creates an iterator of date times that match with the cron value after the given date.
1036    ///
1037    /// # Example
1038    /// ```
1039    /// use saffron::Cron;
1040    /// use chrono::prelude::*;
1041    ///
1042    /// let cron = "*/10 * * * *".parse::<Cron>().expect("Couldn't parse expression!");
1043    /// for time in cron.iter_after(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)).take(5) {
1044    ///     // Prints
1045    ///     // 1970-01-01 00:10:00 UTC
1046    ///     // 1970-01-01 00:20:00 UTC
1047    ///     // 1970-01-01 00:30:00 UTC
1048    ///     // 1970-01-01 00:40:00 UTC
1049    ///     // 1970-01-01 00:50:00 UTC
1050    ///     println!("{}", time)
1051    /// }
1052    /// ```
1053    #[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    /// Returns the next time the cron will match including the given date.
1069    ///
1070    /// # Example
1071    /// ```
1072    /// use saffron::Cron;
1073    /// use chrono::prelude::*;
1074    ///
1075    /// let cron = "*/10 * * * *".parse::<Cron>().expect("Couldn't parse expression!");
1076    /// let date = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
1077    /// // the given date matches the expression, so we get the same date back (truncated)
1078    /// assert_eq!(cron.next_from(date), Some(date));
1079    /// ```
1080    #[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    /// Returns the next time the cron will match after the given date.
1095    ///
1096    /// # Example
1097    /// ```
1098    /// use saffron::Cron;
1099    /// use chrono::prelude::*;
1100    ///
1101    /// let cron = "*/10 * * * *".parse::<Cron>().expect("Couldn't parse expression!");
1102    /// let date = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0);
1103    /// assert_eq!(cron.next_after(date), date.with_minute(10));
1104    /// ```
1105    #[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    /// Gets the next minute (current inclusive) matching the cron expression, or none if the current
1159    /// minute / no upcoming minute in the hour matches. The current minute is a value 0-59.
1160    fn find_next_minute(&self, current_minute: u32) -> Option<u32> {
1161        let Minutes(map) = self.minutes;
1162        // clear the minutes we're already past
1163        let bottom_cleared = (map >> current_minute) << current_minute;
1164        // count trailing zeroes to find the first set. if none is set, we get back the number of
1165        // bits in the integer
1166        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    /// Gets the next matching (current inclusive) hour in the cron expression. The returned matching
1175    /// hour is a value 0-23.
1176    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    /// Gets the next matching (current inclusive) day of the month or day of the week that
1188    /// matches the cron expression. The returned matching day is a value 0-30.
1189    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    /// Gets the next matching (current inclusive) day of the month that matches the cron expression.
1208    /// The returned matching day is a value 0-30.
1209    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                // 'L'
1214                0 => Some(days_in_month - 1),
1215                // 'L-3'
1216                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                // 'LW'
1227                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                // 'L-3W'
1241                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    /// Gets the next matching (current inclusive) day of the week that matches the cron expression.
1306    /// The returned matching day is a value 0-30.
1307    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                // calculate an offset that can be added to the current day to get what would be a day
1314                // of a week where that day is the expected weekday for the cron
1315                let weekday_offset = if cron_weekday < current_weekday {
1316                    // example:
1317                    // current: Thursday, expected: Tuesday
1318                    // 7 - (4 - 2) = 5
1319                    // October 0th 2020 (Thursday) + 5 = October 5th 2020 (Tuesday)
1320                    7 - (current_weekday - cron_weekday)
1321                } else {
1322                    // example:
1323                    // expected: Thursday, current: Tuesday
1324                    // (4 - 2) = 2
1325                    // October 5th 2020 (Tuesday) + 2 = October 7th 2020 (Thursday)
1326                    cron_weekday - current_weekday
1327                };
1328                // the remainder of 7 can be used with day0 to determine the first day0 of the
1329                // current day of the week in the month. it doesn't matter if this calculation
1330                // overflows the date out of the month (31st + 5 = 36th) since we're just looking
1331                // for the first day.
1332                let first_week_day = (date.day0() + weekday_offset) % 7;
1333                // using that we can find the last day this weekday occurs in the month
1334                let last_day = match (days_in_month, first_week_day) {
1335                    // special 5 week weekday handling
1336                    (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    /// Gets the weekday for a given day of the month using the current date for the month
1391    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        // I'm bad at math, so here's all the answers instead
1397        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    /// Gets the next matching (current inclusive) month that matches the cron expression.
1468    /// The returned matching month is a value 0-11.
1469    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    /// Return the current time if it matches, otherwise change to `Next` and get the next time
1535    Start(DateTime<Utc>),
1536    /// Return the next time that matches the cron expression after this one
1537    Next(DateTime<Utc>),
1538    /// The end of the iterator
1539    End,
1540}
1541
1542/// An iterator over the times matching the contained cron value.
1543/// Created with [`Cron::iter_from`] and [`Cron::iter_after`].
1544///
1545/// [`Cron::iter_from`]: struct.Cron.html#method.iter_from
1546/// [`Cron::iter_after`]: struct.Cron.html#method.iter_after
1547pub struct CronTimesIter {
1548    cron: Cron,
1549    current: IterTime,
1550}
1551
1552impl CronTimesIter {
1553    /// Returns the underlying cron value.
1554    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() // call back in and get the next item
1570                }
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    /// check to make sure we don't accidentally include any off-by-one errors with ranges
1662    #[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        // The 31st and 1st of January and December,
1686        // at 11:00 PM, 11:59 PM, 12:00 AM, and 12:59 AM
1687        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        // Midnight on every Saturday and Sunday in January
1712        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", // Last day is a Saturday
1848                "2021-05-31T00:00:00+00:00", // Last day is a Monday
1849                "2020-05-29T00:00:00+00:00", // Last day is a Sunday
1850            ],
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", // Offset last day is a Friday
1862                "2021-05-31T00:00:00+00:00", // Offset last day is a Sunday
1863                "2020-05-29T00:00:00+00:00", // Offset last day is a Saturday
1864            ],
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", // First day is a Friday
1876                "2022-05-02T00:00:00+00:00", // First day is a Sunday
1877                "2021-05-03T00:00:00+00:00", // First day is a Saturday
1878            ],
1879        )
1880    }
1881
1882    #[test]
1883    fn parse_check_last_weekday() {
1884        let cron = "0 0 * * 7L"; // the last saturday of every month
1885
1886        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"; // the 5th saturday of every month
1912
1913        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        // all the impls step impls follow the same code, so i'll just test minutes for now
1938        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        // previous code assumed the start was before the end
1956        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}