cron/
schedule.rs

1use chrono::offset::{LocalResult, TimeZone};
2use chrono::{DateTime, Datelike, Timelike, Utc};
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::ops::Bound::{Included, Unbounded};
5
6#[cfg(feature = "serde")]
7use core::fmt;
8#[cfg(feature = "serde")]
9use serde::{
10    de::{self, Visitor},
11    Deserialize, Serialize, Serializer,
12};
13
14use crate::ordinal::*;
15use crate::queries::*;
16use crate::time_unit::*;
17
18impl From<Schedule> for String {
19    fn from(schedule: Schedule) -> String {
20        schedule.source
21    }
22}
23
24#[derive(Clone, Debug, Eq)]
25pub struct Schedule {
26    source: String,
27    fields: ScheduleFields,
28}
29
30impl Schedule {
31    pub(crate) fn new(source: String, fields: ScheduleFields) -> Schedule {
32        Schedule { source, fields }
33    }
34
35    fn next_after<Z>(&self, after: &DateTime<Z>) -> LocalResult<DateTime<Z>>
36    where
37        Z: TimeZone,
38    {
39        let mut query = NextAfterQuery::from(after);
40        for year in self
41            .fields
42            .years
43            .ordinals()
44            .range((Included(query.year_lower_bound()), Unbounded))
45            .cloned()
46        {
47            // It's a future year, the current year's range is irrelevant.
48            if year > after.year() as u32 {
49                query.reset_month();
50                query.reset_day_of_month();
51            }
52            let month_start = query.month_lower_bound();
53            if !self.fields.months.ordinals().contains(&month_start) {
54                query.reset_month();
55            }
56            let month_range = (Included(month_start), Included(Months::inclusive_max()));
57            for month in self.fields.months.ordinals().range(month_range).cloned() {
58                let day_of_month_start = query.day_of_month_lower_bound();
59                if !self
60                    .fields
61                    .days_of_month
62                    .ordinals()
63                    .contains(&day_of_month_start)
64                {
65                    query.reset_day_of_month();
66                }
67                let day_of_month_end = days_in_month(month, year);
68                let day_of_month_range = (
69                    Included(day_of_month_start.min(day_of_month_end)),
70                    Included(day_of_month_end),
71                );
72
73                'day_loop: for day_of_month in self
74                    .fields
75                    .days_of_month
76                    .ordinals()
77                    .range(day_of_month_range)
78                    .cloned()
79                {
80                    let hour_start = query.hour_lower_bound();
81                    if !self.fields.hours.ordinals().contains(&hour_start) {
82                        query.reset_hour();
83                    }
84                    let hour_range = (Included(hour_start), Included(Hours::inclusive_max()));
85
86                    for hour in self.fields.hours.ordinals().range(hour_range).cloned() {
87                        let minute_start = query.minute_lower_bound();
88                        if !self.fields.minutes.ordinals().contains(&minute_start) {
89                            query.reset_minute();
90                        }
91                        let minute_range =
92                            (Included(minute_start), Included(Minutes::inclusive_max()));
93
94                        for minute in self.fields.minutes.ordinals().range(minute_range).cloned() {
95                            let second_start = query.second_lower_bound();
96                            if !self.fields.seconds.ordinals().contains(&second_start) {
97                                query.reset_second();
98                            }
99                            let second_range =
100                                (Included(second_start), Included(Seconds::inclusive_max()));
101
102                            for second in
103                                self.fields.seconds.ordinals().range(second_range).cloned()
104                            {
105                                let timezone = after.timezone();
106                                let candidate = match timezone.with_ymd_and_hms(
107                                    year as i32,
108                                    month,
109                                    day_of_month,
110                                    hour,
111                                    minute,
112                                    second,
113                                ) {
114                                    LocalResult::None => continue,
115                                    candidate => candidate,
116                                };
117                                if !self.fields.days_of_week.ordinals().contains(
118                                    &candidate
119                                        .clone()
120                                        .latest()
121                                        .unwrap()
122                                        .weekday()
123                                        .number_from_sunday(),
124                                ) {
125                                    continue 'day_loop;
126                                }
127                                return candidate;
128                            }
129                            query.reset_minute();
130                        } // End of minutes range
131                        query.reset_hour();
132                    } // End of hours range
133                    query.reset_day_of_month();
134                } // End of Day of Month range
135                query.reset_month();
136            } // End of Month range
137        }
138
139        // We ran out of dates to try.
140        LocalResult::None
141    }
142
143    fn prev_from<Z>(&self, before: &DateTime<Z>) -> LocalResult<DateTime<Z>>
144    where
145        Z: TimeZone,
146    {
147        let mut query = PrevFromQuery::from(before);
148        for year in self
149            .fields
150            .years
151            .ordinals()
152            .range((Unbounded, Included(query.year_upper_bound())))
153            .rev()
154            .cloned()
155        {
156            let month_start = query.month_upper_bound();
157
158            if !self.fields.months.ordinals().contains(&month_start) {
159                query.reset_month();
160            }
161            let month_range = (Included(Months::inclusive_min()), Included(month_start));
162
163            for month in self
164                .fields
165                .months
166                .ordinals()
167                .range(month_range)
168                .rev()
169                .cloned()
170            {
171                let day_of_month_end = query.day_of_month_upper_bound();
172                if !self
173                    .fields
174                    .days_of_month
175                    .ordinals()
176                    .contains(&day_of_month_end)
177                {
178                    query.reset_day_of_month();
179                }
180
181                let day_of_month_end = days_in_month(month, year).min(day_of_month_end);
182
183                let day_of_month_range = (
184                    Included(DaysOfMonth::inclusive_min()),
185                    Included(day_of_month_end),
186                );
187
188                'day_loop: for day_of_month in self
189                    .fields
190                    .days_of_month
191                    .ordinals()
192                    .range(day_of_month_range)
193                    .rev()
194                    .cloned()
195                {
196                    let hour_start = query.hour_upper_bound();
197                    if !self.fields.hours.ordinals().contains(&hour_start) {
198                        query.reset_hour();
199                    }
200                    let hour_range = (Included(Hours::inclusive_min()), Included(hour_start));
201
202                    for hour in self
203                        .fields
204                        .hours
205                        .ordinals()
206                        .range(hour_range)
207                        .rev()
208                        .cloned()
209                    {
210                        let minute_start = query.minute_upper_bound();
211                        if !self.fields.minutes.ordinals().contains(&minute_start) {
212                            query.reset_minute();
213                        }
214                        let minute_range =
215                            (Included(Minutes::inclusive_min()), Included(minute_start));
216
217                        for minute in self
218                            .fields
219                            .minutes
220                            .ordinals()
221                            .range(minute_range)
222                            .rev()
223                            .cloned()
224                        {
225                            let second_start = query.second_upper_bound();
226                            if !self.fields.seconds.ordinals().contains(&second_start) {
227                                query.reset_second();
228                            }
229                            let second_range =
230                                (Included(Seconds::inclusive_min()), Included(second_start));
231
232                            for second in self
233                                .fields
234                                .seconds
235                                .ordinals()
236                                .range(second_range)
237                                .rev()
238                                .cloned()
239                            {
240                                let timezone = before.timezone();
241                                let candidate = match timezone.with_ymd_and_hms(
242                                    year as i32,
243                                    month,
244                                    day_of_month,
245                                    hour,
246                                    minute,
247                                    second,
248                                ) {
249                                    LocalResult::None => continue,
250                                    some => some,
251                                };
252                                if !self.fields.days_of_week.ordinals().contains(
253                                    &candidate
254                                        .clone()
255                                        .latest()
256                                        .unwrap()
257                                        .weekday()
258                                        .number_from_sunday(),
259                                ) {
260                                    continue 'day_loop;
261                                }
262                                return candidate;
263                            }
264                            query.reset_minute();
265                        } // End of minutes range
266                        query.reset_hour();
267                    } // End of hours range
268                    query.reset_day_of_month();
269                } // End of Day of Month range
270                query.reset_month();
271            } // End of Month range
272        }
273
274        // We ran out of dates to try.
275        LocalResult::None
276    }
277
278    /// Provides an iterator which will return each DateTime that matches the schedule starting with
279    /// the current time if applicable.
280    pub fn upcoming<Z>(&self, timezone: Z) -> ScheduleIterator<'_, Z>
281    where
282        Z: TimeZone,
283    {
284        self.after(&timezone.from_utc_datetime(&Utc::now().naive_utc()))
285    }
286
287    /// The same, but with an iterator with a static ownership
288    pub fn upcoming_owned<Z: TimeZone>(&self, timezone: Z) -> OwnedScheduleIterator<Z> {
289        self.after_owned(timezone.from_utc_datetime(&Utc::now().naive_utc()))
290    }
291
292    /// Like the `upcoming` method, but allows you to specify a start time other than the present.
293    pub fn after<Z>(&self, after: &DateTime<Z>) -> ScheduleIterator<'_, Z>
294    where
295        Z: TimeZone,
296    {
297        ScheduleIterator::new(self, after)
298    }
299
300    /// The same, but with a static ownership.
301    pub fn after_owned<Z: TimeZone>(&self, after: DateTime<Z>) -> OwnedScheduleIterator<Z> {
302        OwnedScheduleIterator::new(self.clone(), after)
303    }
304
305    pub fn includes<Z>(&self, date_time: DateTime<Z>) -> bool
306    where
307        Z: TimeZone,
308    {
309        self.fields.years.includes(date_time.year() as Ordinal)
310            && self.fields.months.includes(date_time.month() as Ordinal)
311            && self
312                .fields
313                .days_of_week
314                .includes(date_time.weekday().number_from_sunday())
315            && self
316                .fields
317                .days_of_month
318                .includes(date_time.day() as Ordinal)
319            && self.fields.hours.includes(date_time.hour() as Ordinal)
320            && self.fields.minutes.includes(date_time.minute() as Ordinal)
321            && self.fields.seconds.includes(date_time.second() as Ordinal)
322    }
323
324    /// Returns a [TimeUnitSpec] describing the years included in this [Schedule].
325    pub fn years(&self) -> &impl TimeUnitSpec {
326        &self.fields.years
327    }
328
329    /// Returns a [TimeUnitSpec] describing the months of the year included in this [Schedule].
330    pub fn months(&self) -> &impl TimeUnitSpec {
331        &self.fields.months
332    }
333
334    /// Returns a [TimeUnitSpec] describing the days of the month included in this [Schedule].
335    pub fn days_of_month(&self) -> &impl TimeUnitSpec {
336        &self.fields.days_of_month
337    }
338
339    /// Returns a [TimeUnitSpec] describing the days of the week included in this [Schedule].
340    pub fn days_of_week(&self) -> &impl TimeUnitSpec {
341        &self.fields.days_of_week
342    }
343
344    /// Returns a [TimeUnitSpec] describing the hours of the day included in this [Schedule].
345    pub fn hours(&self) -> &impl TimeUnitSpec {
346        &self.fields.hours
347    }
348
349    /// Returns a [TimeUnitSpec] describing the minutes of the hour included in this [Schedule].
350    pub fn minutes(&self) -> &impl TimeUnitSpec {
351        &self.fields.minutes
352    }
353
354    /// Returns a [TimeUnitSpec] describing the seconds of the minute included in this [Schedule].
355    pub fn seconds(&self) -> &impl TimeUnitSpec {
356        &self.fields.seconds
357    }
358
359    pub fn timeunitspec_eq(&self, other: &Schedule) -> bool {
360        self.fields == other.fields
361    }
362
363    /// Returns a reference to the source cron expression.
364    pub fn source(&self) -> &str {
365        &self.source
366    }
367}
368
369impl Display for Schedule {
370    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
371        write!(f, "{}", self.source)
372    }
373}
374
375impl PartialEq for Schedule {
376    fn eq(&self, other: &Schedule) -> bool {
377        self.source == other.source
378    }
379}
380
381#[derive(Clone, Debug, PartialEq, Eq)]
382pub struct ScheduleFields {
383    years: Years,
384    days_of_week: DaysOfWeek,
385    months: Months,
386    days_of_month: DaysOfMonth,
387    hours: Hours,
388    minutes: Minutes,
389    seconds: Seconds,
390}
391
392impl ScheduleFields {
393    pub(crate) fn new(
394        seconds: Seconds,
395        minutes: Minutes,
396        hours: Hours,
397        days_of_month: DaysOfMonth,
398        months: Months,
399        days_of_week: DaysOfWeek,
400        years: Years,
401    ) -> ScheduleFields {
402        ScheduleFields {
403            years,
404            days_of_week,
405            months,
406            days_of_month,
407            hours,
408            minutes,
409            seconds,
410        }
411    }
412}
413
414pub struct ScheduleIterator<'a, Z>
415where
416    Z: TimeZone,
417{
418    schedule: &'a Schedule,
419    previous_datetime: Option<DateTime<Z>>,
420    later_datetime: Option<DateTime<Z>>,
421    earlier_datetime: Option<DateTime<Z>>,
422}
423//TODO: Cutoff datetime?
424
425impl<'a, Z> ScheduleIterator<'a, Z>
426where
427    Z: TimeZone,
428{
429    fn new(schedule: &'a Schedule, starting_datetime: &DateTime<Z>) -> Self {
430        ScheduleIterator {
431            schedule,
432            previous_datetime: Some(starting_datetime.clone()),
433            later_datetime: None,
434            earlier_datetime: None,
435        }
436    }
437}
438
439impl<Z> Iterator for ScheduleIterator<'_, Z>
440where
441    Z: TimeZone,
442{
443    type Item = DateTime<Z>;
444
445    fn next(&mut self) -> Option<DateTime<Z>> {
446        let previous = self.previous_datetime.take()?;
447
448        if let Some(later) = self.later_datetime.take() {
449            self.previous_datetime = Some(later.clone());
450            Some(later)
451        } else {
452            match self.schedule.next_after(&previous) {
453                LocalResult::Single(next) => {
454                    self.previous_datetime = Some(next.clone());
455                    Some(next)
456                }
457                LocalResult::Ambiguous(earlier, later) => {
458                    self.previous_datetime = Some(earlier.clone());
459                    self.later_datetime = Some(later);
460                    Some(earlier)
461                }
462                LocalResult::None => None,
463            }
464        }
465    }
466}
467
468impl<Z> DoubleEndedIterator for ScheduleIterator<'_, Z>
469where
470    Z: TimeZone,
471{
472    fn next_back(&mut self) -> Option<Self::Item> {
473        let previous = self.previous_datetime.take()?;
474
475        if let Some(earlier) = self.earlier_datetime.take() {
476            self.previous_datetime = Some(earlier.clone());
477            Some(earlier)
478        } else {
479            match self.schedule.prev_from(&previous) {
480                LocalResult::Single(prev) => {
481                    self.previous_datetime = Some(prev.clone());
482                    Some(prev)
483                }
484                LocalResult::Ambiguous(earlier, later) => {
485                    self.previous_datetime = Some(later.clone());
486                    self.earlier_datetime = Some(earlier);
487                    Some(later)
488                }
489                LocalResult::None => None,
490            }
491        }
492    }
493}
494
495/// A `ScheduleIterator` with a static lifetime.
496pub struct OwnedScheduleIterator<Z>
497where
498    Z: TimeZone,
499{
500    schedule: Schedule,
501    previous_datetime: Option<DateTime<Z>>,
502    // In the case of the Daylight Savings Time transition where an hour is
503    // gained, store the time that occurs twice.  Depending on which direction
504    // the iteration goes, this needs to be stored separately to keep the
505    // direction of time (becoming earlier or later) consistent.
506    later_datetime: Option<DateTime<Z>>,
507    earlier_datetime: Option<DateTime<Z>>,
508}
509
510impl<Z> OwnedScheduleIterator<Z>
511where
512    Z: TimeZone,
513{
514    pub fn new(schedule: Schedule, starting_datetime: DateTime<Z>) -> Self {
515        Self {
516            schedule,
517            previous_datetime: Some(starting_datetime),
518            later_datetime: None,
519            earlier_datetime: None,
520        }
521    }
522}
523
524impl<Z> Iterator for OwnedScheduleIterator<Z>
525where
526    Z: TimeZone,
527{
528    type Item = DateTime<Z>;
529
530    fn next(&mut self) -> Option<DateTime<Z>> {
531        let previous = self.previous_datetime.take()?;
532
533        if let Some(later) = self.later_datetime.take() {
534            self.previous_datetime = Some(later.clone());
535            Some(later)
536        } else {
537            match self.schedule.next_after(&previous) {
538                LocalResult::Single(next) => {
539                    self.previous_datetime = Some(next.clone());
540                    Some(next)
541                }
542                // Handle an "Ambiguous" time, such as during the end of
543                // Daylight Savings Time, transitioning from BST to GMT, where
544                // for example, in London, 2AM occurs twice when the hour is
545                // moved back during the fall.
546                LocalResult::Ambiguous(earlier, later) => {
547                    self.previous_datetime = Some(earlier.clone());
548                    self.later_datetime = Some(later);
549                    Some(earlier)
550                }
551                LocalResult::None => None,
552            }
553        }
554    }
555}
556
557impl<Z: TimeZone> DoubleEndedIterator for OwnedScheduleIterator<Z> {
558    fn next_back(&mut self) -> Option<Self::Item> {
559        let previous = self.previous_datetime.take()?;
560
561        if let Some(earlier) = self.earlier_datetime.take() {
562            self.previous_datetime = Some(earlier.clone());
563            Some(earlier)
564        } else {
565            match self.schedule.prev_from(&previous) {
566                LocalResult::Single(prev) => {
567                    self.previous_datetime = Some(prev.clone());
568                    Some(prev)
569                }
570                // Handle an "Ambiguous" time, such as during the end of
571                // Daylight Savings Time, transitioning from BST to GMT, where
572                // for example, in London, 2AM occurs twice when the hour is
573                // moved back during the fall.
574                LocalResult::Ambiguous(earlier, later) => {
575                    self.previous_datetime = Some(later.clone());
576                    self.earlier_datetime = Some(earlier);
577                    Some(later)
578                }
579                LocalResult::None => None,
580            }
581        }
582    }
583}
584
585fn is_leap_year(year: Ordinal) -> bool {
586    let by_four = year % 4 == 0;
587    let by_hundred = year % 100 == 0;
588    let by_four_hundred = year % 400 == 0;
589    by_four && ((!by_hundred) || by_four_hundred)
590}
591
592fn days_in_month(month: Ordinal, year: Ordinal) -> u32 {
593    let is_leap_year = is_leap_year(year);
594    match month {
595        9 | 4 | 6 | 11 => 30,
596        2 if is_leap_year => 29,
597        2 => 28,
598        _ => 31,
599    }
600}
601
602#[cfg(feature = "serde")]
603struct ScheduleVisitor;
604
605#[cfg(feature = "serde")]
606impl Visitor<'_> for ScheduleVisitor {
607    type Value = Schedule;
608
609    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
610        formatter.write_str("a valid cron expression")
611    }
612
613    // Supporting `Deserializer`s shall provide an owned `String`.
614    //
615    // The `Schedule` will decode from a `&str` to it,
616    // then store the owned `String` as `Schedule::source`.
617    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
618    where
619        E: de::Error,
620    {
621        Schedule::try_from(v).map_err(de::Error::custom)
622    }
623
624    // `Deserializer`s not providing an owned `String`
625    // shall provide a `&str`.
626    //
627    // The `Schedule` will decode from the `&str`,
628    // then clone into the heap to store as an owned `String`
629    // as `Schedule::source`.
630    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
631    where
632        E: de::Error,
633    {
634        Schedule::try_from(v).map_err(de::Error::custom)
635    }
636}
637
638#[cfg(feature = "serde")]
639impl Serialize for Schedule {
640    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
641    where
642        S: Serializer,
643    {
644        serializer.serialize_str(self.source())
645    }
646}
647
648#[cfg(feature = "serde")]
649impl<'de> Deserialize<'de> for Schedule {
650    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
651    where
652        D: serde::Deserializer<'de>,
653    {
654        // Hint that the `Deserialize` type `Schedule`
655        // would benefit from taking ownership of
656        // buffered data owned by the `Deserializer`:
657        //
658        // The deserialization "happy path" decodes from a `&str`,
659        // then stores the source as owned `String`.
660        //
661        // Thus, the optimized happy path receives an owned `String`
662        // if the `Deserializer` in use supports providing one.
663        deserializer.deserialize_string(ScheduleVisitor)
664    }
665}
666
667#[cfg(test)]
668mod test {
669    use chrono::Duration;
670    #[cfg(feature = "serde")]
671    use serde_test::{assert_tokens, Token};
672
673    use super::*;
674    use std::str::FromStr;
675
676    #[cfg(feature = "serde")]
677    #[test]
678    fn test_ser_de_schedule_tokens() {
679        let schedule = Schedule::from_str("* * * * * * *").expect("valid format");
680        assert_tokens(&schedule, &[Token::String("* * * * * * *")])
681    }
682
683    #[cfg(feature = "serde")]
684    #[test]
685    fn test_invalid_ser_de_schedule_tokens() {
686        use serde_test::assert_de_tokens_error;
687
688        assert_de_tokens_error::<Schedule>(
689            &[Token::String(
690                "definitively an invalid value for a cron schedule!",
691            )],
692            "definitively an invalid value for a cron schedule!\n\
693                ^\n\
694                The 'Seconds' field does not support using names. 'definitively' specified.",
695        );
696    }
697
698    #[cfg(feature = "serde")]
699    #[test]
700    fn test_ser_de_schedule_shorthand() {
701        let serialized = postcard::to_stdvec(&Schedule::try_from("@hourly").expect("valid format"))
702            .expect("serializable schedule");
703
704        let schedule: Schedule =
705            postcard::from_bytes(&serialized).expect("deserializable schedule");
706
707        let starting_date = Utc.with_ymd_and_hms(2017, 2, 25, 22, 29, 36).unwrap();
708        assert!([
709            Utc.with_ymd_and_hms(2017, 2, 25, 23, 0, 0).unwrap(),
710            Utc.with_ymd_and_hms(2017, 2, 26, 0, 0, 0).unwrap(),
711            Utc.with_ymd_and_hms(2017, 2, 26, 1, 0, 0).unwrap(),
712        ]
713        .into_iter()
714        .eq(schedule.after(&starting_date).take(3)));
715    }
716
717    #[cfg(feature = "serde")]
718    #[test]
719    fn test_ser_de_schedule_period_values_range() {
720        let serialized =
721            postcard::to_stdvec(&Schedule::try_from("0 0 0 1-31/10 * ?").expect("valid format"))
722                .expect("serializable schedule");
723
724        let schedule: Schedule =
725            postcard::from_bytes(&serialized).expect("deserializable schedule");
726
727        let starting_date = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
728        assert!([
729            Utc.with_ymd_and_hms(2020, 1, 11, 0, 0, 0).unwrap(),
730            Utc.with_ymd_and_hms(2020, 1, 21, 0, 0, 0).unwrap(),
731            Utc.with_ymd_and_hms(2020, 1, 31, 0, 0, 0).unwrap(),
732            Utc.with_ymd_and_hms(2020, 2, 1, 0, 0, 0).unwrap(),
733            Utc.with_ymd_and_hms(2020, 2, 11, 0, 0, 0).unwrap(),
734            Utc.with_ymd_and_hms(2020, 2, 21, 0, 0, 0).unwrap(),
735            Utc.with_ymd_and_hms(2020, 3, 1, 0, 0, 0).unwrap(),
736        ]
737        .into_iter()
738        .eq(schedule.after(&starting_date).take(7)));
739    }
740
741    #[test]
742    fn test_next_and_prev_from() {
743        let expression = "0 5,13,40-42 17 1 Jan *";
744        let schedule = Schedule::from_str(expression).unwrap();
745
746        let next = schedule.next_after(&Utc::now());
747        println!("NEXT AFTER for {} {:?}", expression, next);
748        assert!(next.single().is_some());
749
750        let next2 = schedule.next_after(&next.unwrap());
751        println!("NEXT2 AFTER for {} {:?}", expression, next2);
752        assert!(next2.single().is_some());
753
754        let prev = schedule.prev_from(&next2.unwrap());
755        println!("PREV FROM for {} {:?}", expression, prev);
756        assert!(prev.single().is_some());
757        assert_eq!(prev, next);
758
759        let prev2 = schedule.prev_from(&(next2.unwrap() + Duration::nanoseconds(100)));
760        println!("PREV2 FROM for {} {:?}", expression, prev2);
761        assert!(prev2.single().is_some());
762        assert_eq!(prev2, next2);
763    }
764
765    #[test]
766    fn test_next_after_past_date_next_year() {
767        // Schedule after 2021-10-27
768        let starting_point = Utc.with_ymd_and_hms(2021, 10, 27, 0, 0, 0).unwrap();
769
770        // Triggers on 2022-06-01. Note that the month and day are smaller than
771        // the month and day in `starting_point`.
772        let expression = "0 5 17 1 6 ? 2022".to_string();
773        let schedule = Schedule::from_str(&expression).unwrap();
774        let next = schedule.next_after(&starting_point);
775        println!("NEXT AFTER for {} {:?}", expression, next);
776        assert!(next.single().is_some());
777    }
778
779    #[test]
780    fn test_prev_from() {
781        let expression = "0 5,13,40-42 17 1 Jan *";
782        let schedule = Schedule::from_str(expression).unwrap();
783        let prev = schedule.prev_from(&Utc::now());
784        println!("PREV FROM for {} {:?}", expression, prev);
785        assert!(prev.single().is_some());
786    }
787
788    #[test]
789    fn test_next_after() {
790        let expression = "0 5,13,40-42 17 1 Jan *";
791        let schedule = Schedule::from_str(expression).unwrap();
792        let next = schedule.next_after(&Utc::now());
793        println!("NEXT AFTER for {} {:?}", expression, next);
794        assert!(next.single().is_some());
795    }
796
797    #[test]
798    fn test_upcoming_utc() {
799        let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
800        let schedule = Schedule::from_str(expression).unwrap();
801        let mut upcoming = schedule.upcoming(Utc);
802        let next1 = upcoming.next();
803        assert!(next1.is_some());
804        let next2 = upcoming.next();
805        assert!(next2.is_some());
806        let next3 = upcoming.next();
807        assert!(next3.is_some());
808        println!("Upcoming 1 for {} {:?}", expression, next1);
809        println!("Upcoming 2 for {} {:?}", expression, next2);
810        println!("Upcoming 3 for {} {:?}", expression, next3);
811    }
812
813    #[test]
814    fn test_upcoming_utc_owned() {
815        let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
816        let schedule = Schedule::from_str(expression).unwrap();
817        let mut upcoming = schedule.upcoming_owned(Utc);
818        let next1 = upcoming.next();
819        assert!(next1.is_some());
820        let next2 = upcoming.next();
821        assert!(next2.is_some());
822        let next3 = upcoming.next();
823        assert!(next3.is_some());
824        println!("Upcoming 1 for {} {:?}", expression, next1);
825        println!("Upcoming 2 for {} {:?}", expression, next2);
826        println!("Upcoming 3 for {} {:?}", expression, next3);
827    }
828
829    #[test]
830    fn test_upcoming_rev_utc() {
831        let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
832        let schedule = Schedule::from_str(expression).unwrap();
833        let mut upcoming = schedule.upcoming(Utc).rev();
834        let prev1 = upcoming.next();
835        assert!(prev1.is_some());
836        let prev2 = upcoming.next();
837        assert!(prev2.is_some());
838        let prev3 = upcoming.next();
839        assert!(prev3.is_some());
840        println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
841        println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
842        println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
843    }
844
845    #[test]
846    fn test_upcoming_rev_utc_owned() {
847        let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
848        let schedule = Schedule::from_str(expression).unwrap();
849        let mut upcoming = schedule.upcoming_owned(Utc).rev();
850        let prev1 = upcoming.next();
851        assert!(prev1.is_some());
852        let prev2 = upcoming.next();
853        assert!(prev2.is_some());
854        let prev3 = upcoming.next();
855        assert!(prev3.is_some());
856        println!("Prev Upcoming 1 for {} {:?}", expression, prev1);
857        println!("Prev Upcoming 2 for {} {:?}", expression, prev2);
858        println!("Prev Upcoming 3 for {} {:?}", expression, prev3);
859    }
860
861    #[test]
862    fn test_upcoming_local() {
863        use chrono::Local;
864        let expression = "0 0,30 0,6,12,18 1,15 Jan-March Thurs";
865        let schedule = Schedule::from_str(expression).unwrap();
866        let mut upcoming = schedule.upcoming(Local);
867        let next1 = upcoming.next();
868        assert!(next1.is_some());
869        let next2 = upcoming.next();
870        assert!(next2.is_some());
871        let next3 = upcoming.next();
872        assert!(next3.is_some());
873        println!("Upcoming 1 for {} {:?}", expression, next1);
874        println!("Upcoming 2 for {} {:?}", expression, next2);
875        println!("Upcoming 3 for {} {:?}", expression, next3);
876    }
877
878    #[test]
879    fn test_schedule_to_string() {
880        let expression = "* 1,2,3 * * * *";
881        let schedule: Schedule = Schedule::from_str(expression).unwrap();
882        let result = String::from(schedule);
883        assert_eq!(expression, result);
884    }
885
886    #[test]
887    fn test_display_schedule() {
888        use std::fmt::Write;
889        let expression = "@monthly";
890        let schedule = Schedule::from_str(expression).unwrap();
891        let mut result = String::new();
892        write!(result, "{}", schedule).unwrap();
893        assert_eq!(expression, result);
894    }
895
896    #[test]
897    fn test_valid_from_str() {
898        let schedule = Schedule::from_str("0 0,30 0,6,12,18 1,15 Jan-March Thurs");
899        schedule.unwrap();
900    }
901
902    #[test]
903    fn test_invalid_from_str() {
904        let schedule = Schedule::from_str("cheesecake 0,30 0,6,12,18 1,15 Jan-March Thurs");
905        assert!(schedule.is_err());
906    }
907
908    #[test]
909    fn test_no_panic_on_nonexistent_time_after() {
910        use chrono::offset::TimeZone;
911        use chrono_tz::Tz;
912
913        let schedule_tz: Tz = "Europe/London".parse().unwrap();
914        let dt = schedule_tz
915            .with_ymd_and_hms(2019, 10, 27, 0, 3, 29)
916            .unwrap()
917            .checked_add_signed(chrono::Duration::hours(1)) // puts it in the middle of the DST transition
918            .unwrap();
919        let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
920        let next = schedule.after(&dt).next().unwrap();
921        assert!(next > dt); // test is ensuring line above does not panic
922    }
923
924    #[test]
925    fn test_no_panic_on_nonexistent_time_before() {
926        use chrono::offset::TimeZone;
927        use chrono_tz::Tz;
928
929        let schedule_tz: Tz = "Europe/London".parse().unwrap();
930        let dt = schedule_tz
931            .with_ymd_and_hms(2019, 10, 27, 0, 3, 29)
932            .unwrap()
933            .checked_add_signed(chrono::Duration::hours(1)) // puts it in the middle of the DST transition
934            .unwrap();
935        let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
936        let prev = schedule.after(&dt).nth_back(1).unwrap();
937        assert!(prev < dt); // test is ensuring line above does not panic
938    }
939
940    #[test]
941    fn test_no_panic_on_leap_day_time_after() {
942        let dt = chrono::DateTime::parse_from_rfc3339("2024-02-29T10:00:00.000+08:00").unwrap();
943        let schedule = Schedule::from_str("0 0 0 * * * 2100").unwrap();
944        let next = schedule.after(&dt).next().unwrap();
945        assert!(next > dt); // test is ensuring line above does not panic
946    }
947
948    #[test]
949    fn test_time_unit_spec_equality() {
950        let schedule_1 = Schedule::from_str("@weekly").unwrap();
951        let schedule_2 = Schedule::from_str("0 0 0 * * 1 *").unwrap();
952        let schedule_3 = Schedule::from_str("0 0 0 * * 1-7 *").unwrap();
953        let schedule_4 = Schedule::from_str("0 0 0 * * * *").unwrap();
954        assert_ne!(schedule_1, schedule_2);
955        assert!(schedule_1.timeunitspec_eq(&schedule_2));
956        assert!(schedule_3.timeunitspec_eq(&schedule_4));
957    }
958
959    #[test]
960    fn test_dst_ambiguous_time_after() {
961        use chrono_tz::Tz;
962
963        let schedule_tz: Tz = "America/Chicago".parse().unwrap();
964        let dt = schedule_tz
965            .with_ymd_and_hms(2022, 11, 5, 23, 30, 0)
966            .unwrap();
967        let schedule = Schedule::from_str("0 0 * * * * *").unwrap();
968        let times = schedule
969            .after(&dt)
970            .map(|x| x.to_string())
971            .take(5)
972            .collect::<Vec<_>>();
973        let expected_times = [
974            "2022-11-06 00:00:00 CDT".to_string(),
975            "2022-11-06 01:00:00 CDT".to_string(),
976            "2022-11-06 01:00:00 CST".to_string(), // 1 AM happens again
977            "2022-11-06 02:00:00 CST".to_string(),
978            "2022-11-06 03:00:00 CST".to_string(),
979        ];
980
981        assert_eq!(times.as_slice(), expected_times.as_slice());
982    }
983
984    #[test]
985    fn test_dst_ambiguous_time_before() {
986        use chrono_tz::Tz;
987
988        let schedule_tz: Tz = "America/Chicago".parse().unwrap();
989        let dt = schedule_tz.with_ymd_and_hms(2022, 11, 6, 3, 30, 0).unwrap();
990        let schedule = Schedule::from_str("0 0 * * * * *").unwrap();
991        let times = schedule
992            .after(&dt)
993            .map(|x| x.to_string())
994            .rev()
995            .take(5)
996            .collect::<Vec<_>>();
997        let expected_times = [
998            "2022-11-06 03:00:00 CST".to_string(),
999            "2022-11-06 02:00:00 CST".to_string(),
1000            "2022-11-06 01:00:00 CST".to_string(),
1001            "2022-11-06 01:00:00 CDT".to_string(), // 1 AM happens again
1002            "2022-11-06 00:00:00 CDT".to_string(),
1003        ];
1004
1005        assert_eq!(times.as_slice(), expected_times.as_slice());
1006    }
1007}