cron_lite/
schedule.rs

1use crate::{
2    pattern::{Pattern, PatternItem, PatternType, PatternValueType},
3    utils, CronError, Result,
4};
5use chrono::{offset::LocalResult, DateTime, Datelike, TimeDelta, TimeZone, Timelike};
6#[cfg(feature = "tz")]
7use chrono_tz::Tz;
8use std::{fmt::Display, str::FromStr};
9
10/// Minimum valid year.
11pub const MIN_YEAR: u16 = 1970;
12/// Maximum valid year.
13pub const MAX_YEAR: u16 = 2099;
14
15pub(crate) const MIN_YEAR_STR: &str = "1970";
16
17/// Represents a cron schedule pattern with its methods.
18///
19/// For cron schedule clarification and usage examples, please refer to the [crate documentation](crate).
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "serde", serde(try_from = "String"))]
23#[cfg_attr(feature = "serde", serde(into = "String"))]
24pub struct Schedule {
25    year: Pattern,
26    month: Pattern,
27    dom: Pattern,
28    dow: Pattern,
29    hour: Pattern,
30    minute: Pattern,
31    second: Pattern,
32    #[cfg(feature = "tz")]
33    tz: Option<Tz>,
34}
35
36impl Schedule {
37    /// Parses and validates provided `pattern` and constructs [`Schedule`] instance.
38    ///
39    /// Alternative way to construct [`Schedule`] is to use one of `try_from` or `from_str` methods .
40    ///
41    /// Returns [`CronError`] in a case provided pattern is unparsable or has format errors.
42    pub fn new(pattern: impl Into<String>) -> Result<Self> {
43        let pattern = pattern.into();
44        let mut elements: Vec<&str> = pattern.split_whitespace().collect();
45        #[cfg(feature = "tz")]
46        let mut tz = None;
47
48        // Parse and define TZ, if present
49        #[cfg(feature = "tz")]
50        if elements.len() >= 2 {
51            let tz_elements: Vec<&str> = elements[0].split('=').collect();
52            if tz_elements.len() == 2 && tz_elements[0].to_uppercase() == "TZ" {
53                let tz_str = tz_elements[1];
54                if let Ok(tz_value) = Tz::from_str(tz_str) {
55                    tz = Some(tz_value);
56                    elements.remove(0);
57                } else {
58                    return Err(CronError::InvalidTimeZone(tz_str.to_string()));
59                }
60            }
61        }
62
63        // Check the number of elements in the provided expression and augment it with defaults.
64        if elements.len() == 1 {
65            // Check fo aliases
66            match elements[0] {
67                "@yearly" | "@annually" => elements = vec!["0", "0", "0", "1", "1", "?", "*"],
68                "@monthly" => elements = vec!["0", "0", "0", "1", "*", "?", "*"],
69                "@weekly" => elements = vec!["0", "0", "0", "?", "*", "0", "*"],
70                "@daily" | "@midnight" => elements = vec!["0", "0", "0", "*", "*", "*", "*"],
71                "@hourly" => elements = vec!["0", "0", "*", "*", "*", "*", "*"],
72                _ => return Err(CronError::InvalidCronSchedule(pattern)),
73            }
74        } else if elements.len() == 5 {
75            elements.insert(0, "0");
76            elements.insert(6, "*");
77        } else if elements.len() == 6 {
78            elements.insert(6, "*");
79        } else if elements.len() != 7 {
80            return Err(CronError::InvalidCronSchedule(pattern));
81        }
82
83        // Parse each element.
84        let schedule = Self {
85            second: Pattern::parse(PatternType::Seconds, elements[0])?,
86            minute: Pattern::parse(PatternType::Minutes, elements[1])?,
87            hour: Pattern::parse(PatternType::Hours, elements[2])?,
88            dom: Pattern::parse(PatternType::Doms, elements[3])?,
89            month: Pattern::parse(PatternType::Months, elements[4])?,
90            dow: Pattern::parse(PatternType::Dows, elements[5])?,
91            year: Pattern::parse(PatternType::Years, elements[6])?,
92            #[cfg(feature = "tz")]
93            tz,
94        };
95
96        // Validate DOM and DOW relationship.
97        match (schedule.dom.pattern(), schedule.dow.pattern()) {
98            (PatternItem::Any, PatternItem::Any) => return Err(CronError::InvalidDaysPattern(pattern)),
99            (PatternItem::All, _) | (_, PatternItem::All) | (PatternItem::Any, _) | (_, PatternItem::Any) => {}
100            (_, _) => {
101                return Err(CronError::InvalidDaysPattern(pattern));
102            }
103        }
104
105        Ok(schedule)
106    }
107
108    /// Return time of the upcoming cron event, starting from the provided `current` value (inclusively).
109    ///
110    /// If `tz` feature isn't enabled,
111    /// this method assumes that schedule timezone is the same as timezone of the provided `current` instance.
112    ///
113    /// If `tz` feature is enabled and [schedule uses timezone](crate#schedule-with-timezone),
114    /// then method calculates time of the upcoming event with respect to the schedule's timezone:
115    /// - converts `current` into schedule timezone;
116    /// - calculates upcoming event time;
117    /// - converts obtained upcoming value back to the timezone of the `current` instance.
118    ///
119    /// Returns `None` if there is no time for the upcoming event.
120    #[cfg(not(feature = "tz"))]
121    #[inline]
122    pub fn upcoming<T: TimeZone>(&self, current: &DateTime<T>) -> Option<DateTime<T>> {
123        self.upcoming_impl(current)
124    }
125
126    /// Doc is above.
127    #[cfg(feature = "tz")]
128    pub fn upcoming<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> Option<DateTime<Tz>> {
129        if let Some(schedule_tz) = &self.tz {
130            let current_tz = current.timezone();
131            let current = current.with_timezone(schedule_tz);
132            let result = self.upcoming_impl(&current);
133            result.map(|dt| dt.with_timezone(&current_tz))
134        } else {
135            self.upcoming_impl(current)
136        }
137    }
138
139    /// Return time of the upcoming cron event starting from (including) provided `current` value.
140    ///
141    /// Returns `None` if there is no upcoming event's time.
142    fn upcoming_impl<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> Option<DateTime<Tz>> {
143        // Normalize current time to the start of the whole second.
144        let mut current = if current.nanosecond() > 0 {
145            current
146                .with_nanosecond(0)
147                .unwrap()
148                .checked_add_signed(TimeDelta::seconds(1))
149                .unwrap()
150        } else {
151            current.clone()
152        };
153
154        let mut year = Some(current.year() as PatternValueType);
155        let mut month = Some(current.month() as PatternValueType);
156        let mut dom = Some(current.day() as PatternValueType);
157        let mut hour = Some(current.hour() as PatternValueType);
158        let mut minute = Some(current.minute() as PatternValueType);
159        let mut second = Some(current.second() as PatternValueType);
160        let mut first_iteration = true; // since we don't have `util` loop
161
162        while year.is_none()
163            || month.is_none()
164            || dom.is_none()
165            || hour.is_none()
166            || minute.is_none()
167            || second.is_none()
168            || first_iteration
169        {
170            first_iteration = false;
171
172            // Jump over to the next possible value is needed.
173            if year.is_none() {
174                return None;
175            } else if month.is_none() {
176                inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
177            } else if dom.is_none() {
178                inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
179            } else if hour.is_none() {
180                inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
181            } else if minute.is_none() {
182                inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
183            } else if second.is_none() {
184                inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second)?;
185            }
186
187            current = match current.timezone().with_ymd_and_hms(
188                year? as i32,
189                month? as u32,
190                dom? as u32,
191                hour? as u32,
192                minute? as u32,
193                second? as u32,
194            ) {
195                LocalResult::Single(updated_current) => updated_current,
196                LocalResult::Ambiguous(earliest, _latest) => earliest,
197                LocalResult::None => {
198                    minute = None;
199                    continue;
200                }
201            };
202
203            // Calculate the next possible valid date/time from the current,
204            // with leaping to the first day/hour/... when the current element was changed.
205            year = self.year.next(&mut current);
206            if year.is_some() {
207                month = self.month.next(&mut current);
208                year = Some(current.year() as PatternValueType);
209                if month.is_some() {
210                    // Prepare day of month depending on DOM/DOW pattern types.
211                    dom = match (self.dom.pattern(), self.dow.pattern()) {
212                        (PatternItem::All, PatternItem::All) => self.dom.next(&mut current),
213                        (PatternItem::All, PatternItem::Any) => self.dom.next(&mut current),
214                        (PatternItem::All, _) => self.dow.next(&mut current),
215                        (PatternItem::Any, PatternItem::All) => self.dow.next(&mut current),
216                        (PatternItem::Any, PatternItem::Any) => unreachable!(),
217                        (PatternItem::Any, _) => self.dow.next(&mut current),
218                        (_, PatternItem::All) => self.dom.next(&mut current),
219                        (_, PatternItem::Any) => self.dom.next(&mut current),
220                        (_, _) => unreachable!(),
221                    };
222                    year = Some(current.year() as PatternValueType);
223                    month = Some(current.month() as PatternValueType);
224                    if dom.is_some() {
225                        hour = self.hour.next(&mut current);
226                        year = Some(current.year() as PatternValueType);
227                        month = Some(current.month() as PatternValueType);
228                        dom = Some(current.day() as PatternValueType);
229                        if hour.is_some() {
230                            minute = self.minute.next(&mut current);
231                            year = Some(current.year() as PatternValueType);
232                            month = Some(current.month() as PatternValueType);
233                            dom = Some(current.day() as PatternValueType);
234                            hour = Some(current.hour() as PatternValueType);
235                            if minute.is_some() {
236                                second = self.second.next(&mut current);
237                                year = Some(current.year() as PatternValueType);
238                                month = Some(current.month() as PatternValueType);
239                                dom = Some(current.day() as PatternValueType);
240                                hour = Some(current.hour() as PatternValueType);
241                                minute = Some(current.minute() as PatternValueType);
242                            }
243                        }
244                    }
245                }
246            }
247        }
248
249        match current.timezone().with_ymd_and_hms(
250            year? as i32,
251            month? as u32,
252            dom? as u32,
253            hour? as u32,
254            minute? as u32,
255            second? as u32,
256        ) {
257            LocalResult::Single(current) => Some(current),
258            LocalResult::Ambiguous(earliest, _latest) => Some(earliest),
259            LocalResult::None => None,
260        }
261    }
262
263    /// Returns iterator of events starting from `current` (inclusively).
264    #[inline]
265    pub fn iter<Tz: TimeZone>(&self, current: &DateTime<Tz>) -> impl Iterator<Item = DateTime<Tz>> {
266        ScheduleIterator {
267            schedule: self.clone(),
268            next: self.upcoming(current),
269        }
270    }
271
272    /// Consumes [`Schedule`] and returns iterator of events starting from `current` (inclusively).
273    #[inline]
274    pub fn into_iter<Tz: TimeZone>(self, current: &DateTime<Tz>) -> impl Iterator<Item = DateTime<Tz>> {
275        let next = self.upcoming(current);
276        ScheduleIterator { schedule: self, next }
277    }
278}
279
280/// Contains iterator state.
281#[derive(Debug, Clone, PartialEq, Eq, Hash)]
282pub(crate) struct ScheduleIterator<Tz: TimeZone> {
283    pub(crate) schedule: Schedule,
284    pub(crate) next: Option<DateTime<Tz>>,
285}
286
287impl<Tz: TimeZone> Iterator for ScheduleIterator<Tz> {
288    type Item = DateTime<Tz>;
289
290    fn next(&mut self) -> Option<Self::Item> {
291        let current = self.next.take()?;
292        self.next = self
293            .schedule
294            .upcoming(&current.clone().checked_add_signed(TimeDelta::seconds(1))?);
295        Some(current)
296    }
297}
298
299impl From<Schedule> for String {
300    fn from(value: Schedule) -> Self {
301        value.to_string()
302    }
303}
304
305impl From<&Schedule> for String {
306    fn from(value: &Schedule) -> Self {
307        value.to_string()
308    }
309}
310
311impl TryFrom<String> for Schedule {
312    type Error = CronError;
313
314    fn try_from(value: String) -> Result<Self> {
315        Self::new(value)
316    }
317}
318
319impl TryFrom<&String> for Schedule {
320    type Error = CronError;
321
322    fn try_from(value: &String) -> Result<Self> {
323        Self::new(value)
324    }
325}
326
327impl TryFrom<&str> for Schedule {
328    type Error = CronError;
329
330    fn try_from(value: &str) -> Result<Self> {
331        Self::new(value)
332    }
333}
334
335impl FromStr for Schedule {
336    type Err = CronError;
337
338    fn from_str(s: &str) -> Result<Self> {
339        Self::new(s)
340    }
341}
342
343impl Display for Schedule {
344    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345        #[cfg(not(feature = "tz"))]
346        {
347            write!(
348                f,
349                "{} {} {} {} {} {} {}",
350                self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
351            )
352        }
353
354        #[cfg(feature = "tz")]
355        if let Some(tz) = self.tz {
356            write!(
357                f,
358                "TZ={} {} {} {} {} {} {} {}",
359                tz, self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
360            )
361        } else {
362            write!(
363                f,
364                "{} {} {} {} {} {} {}",
365                self.second, self.minute, self.hour, self.dom, self.month, self.dow, self.year
366            )
367        }
368    }
369}
370
371/// Increments current year and set all other elements to the first valid value.
372#[inline]
373fn inc_year(
374    year: &mut Option<PatternValueType>,
375    month: &mut Option<PatternValueType>,
376    dom: &mut Option<PatternValueType>,
377    hour: &mut Option<PatternValueType>,
378    minute: &mut Option<PatternValueType>,
379    second: &mut Option<PatternValueType>,
380) -> Option<PatternValueType> {
381    if (*year)? < MAX_YEAR {
382        *year = Some((*year)? + 1);
383        *month = Some(1);
384        *dom = Some(1);
385        *hour = Some(0);
386        *minute = Some(0);
387        *second = Some(0);
388
389        *year
390    } else {
391        *year = None;
392        None
393    }
394}
395
396/// Increments current month and set all other elements to the first valid value.
397#[inline]
398fn inc_month(
399    year: &mut Option<PatternValueType>,
400    month: &mut Option<PatternValueType>,
401    dom: &mut Option<PatternValueType>,
402    hour: &mut Option<PatternValueType>,
403    minute: &mut Option<PatternValueType>,
404    second: &mut Option<PatternValueType>,
405) -> Option<PatternValueType> {
406    if (*month)? < 12 {
407        *month = Some((*month)? + 1);
408        *dom = Some(1);
409        *hour = Some(0);
410        *minute = Some(0);
411        *second = Some(0);
412
413        *month
414    } else {
415        inc_year(year, month, dom, hour, minute, second)?;
416        *month
417    }
418}
419
420/// Increments current day of month and set all other elements to the first valid value.
421#[inline]
422fn inc_dom(
423    year: &mut Option<PatternValueType>,
424    month: &mut Option<PatternValueType>,
425    dom: &mut Option<PatternValueType>,
426    hour: &mut Option<PatternValueType>,
427    minute: &mut Option<PatternValueType>,
428    second: &mut Option<PatternValueType>,
429) -> Option<PatternValueType> {
430    if (*dom)? < utils::days_in_month((*year)?, (*month)?) {
431        *dom = Some((*dom)? + 1);
432        *hour = Some(0);
433        *minute = Some(0);
434        *second = Some(0);
435
436        *dom
437    } else {
438        inc_month(year, month, dom, hour, minute, second)?;
439        *dom
440    }
441}
442
443/// Increments current hour and set all other elements to the first valid value.
444#[inline]
445fn inc_hour(
446    year: &mut Option<PatternValueType>,
447    month: &mut Option<PatternValueType>,
448    dom: &mut Option<PatternValueType>,
449    hour: &mut Option<PatternValueType>,
450    minute: &mut Option<PatternValueType>,
451    second: &mut Option<PatternValueType>,
452) -> Option<PatternValueType> {
453    if (*hour)? < 23 {
454        *hour = Some((*hour)? + 1);
455        *minute = Some(0);
456        *second = Some(0);
457
458        *hour
459    } else {
460        inc_dom(year, month, dom, hour, minute, second)?;
461        *hour
462    }
463}
464
465/// Increments current minute and set all other elements to the first valid value.
466#[inline]
467fn inc_minute(
468    year: &mut Option<PatternValueType>,
469    month: &mut Option<PatternValueType>,
470    dom: &mut Option<PatternValueType>,
471    hour: &mut Option<PatternValueType>,
472    minute: &mut Option<PatternValueType>,
473    second: &mut Option<PatternValueType>,
474) -> Option<PatternValueType> {
475    if (*minute)? < 59 {
476        *minute = Some((*minute)? + 1);
477        *second = Some(0);
478
479        *minute
480    } else {
481        inc_hour(year, month, dom, hour, minute, second)?;
482        *minute
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489    use chrono::DateTime;
490    use rstest::rstest;
491    use rstest_reuse::{apply, template};
492    use std::time::Duration;
493
494    #[rstest]
495    #[case("* 0 0 1 1 *", "2024-01-01T00:00:21Z", "2024-01-01T00:00:21+00:00")]
496    #[case("* 0 0 1 1 *", "2024-01-01T01:00:25Z", "2025-01-01T00:00:00+00:00")]
497    #[case("*/5 * * * * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
498    #[case("*/5 * * * * *", "2024-01-01T00:00:01Z", "2024-01-01T00:00:05+00:00")]
499    #[case("0 */15 * * * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
500    #[case("0 */15 * * * *", "2024-01-01T00:01:00Z", "2024-01-01T00:15:00+00:00")]
501    #[case("0 */30 9-17 * * 1-5", "2024-01-01T09:00:00Z", "2024-01-01T09:00:00+00:00")]
502    #[case("0 */30 9-17 * * 1-5", "2024-01-01T09:15:00Z", "2024-01-01T09:30:00+00:00")]
503    #[case("0 */5 * * * *", "2024-01-01T00:01:00Z", "2024-01-01T00:05:00+00:00")]
504    #[case("0 0 */2 * * *", "2024-01-01T01:00:00Z", "2024-01-01T02:00:00+00:00")]
505    #[case("0 0 0 ? * 1-5", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
506    #[case("0 0 0 ? * 1-5", "2024-01-01T00:00:01Z", "2024-01-02T00:00:00+00:00")]
507    #[case("0 0 0 ? * 1-5", "2024-01-05T00:00:01Z", "2024-01-08T00:00:00+00:00")]
508    #[case("0 0 0 * * 1#1", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
509    #[case("0 0 0 * * 1#1", "2024-01-02T00:00:00Z", "2024-02-05T00:00:00+00:00")]
510    #[case("0 0 0 * * 5L", "2024-01-01T00:00:00Z", "2024-01-26T00:00:00+00:00")]
511    #[case("0 0 0 * * 5L", "2024-01-26T00:00:01Z", "2024-02-23T00:00:00+00:00")]
512    #[case("0 0 0 * * 5L", "2024-02-23T00:00:00Z", "2024-02-23T00:00:00+00:00")]
513    #[case("0 0 0 * * 6,0", "2024-01-01T00:00:00Z", "2024-01-06T00:00:00+00:00")]
514    #[case("0 0 0 * * 6,0", "2024-01-06T00:00:01Z", "2024-01-07T00:00:00+00:00")]
515    #[case("0 0 0 * * 6,0", "2024-01-07T00:00:00Z", "2024-01-07T00:00:00+00:00")]
516    #[case("0 0 0 * * 6,0", "2024-01-07T00:00:01Z", "2024-01-13T00:00:00+00:00")]
517    #[case("0 0 0 * * MON", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
518    #[case("0 0 0 * * SUN", "2024-01-01T00:00:00Z", "2024-01-07T00:00:00+00:00")]
519    #[case("0 0 0 1 */2 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
520    #[case("0 0 0 1 */2 *", "2024-02-01T00:00:00Z", "2024-03-01T00:00:00+00:00")]
521    #[case("0 0 0 1 */3 * 1999", "1999-01-01T00:00:00Z", "1999-01-01T00:00:00+00:00")]
522    #[case("0 0 0 1 */3 * 1999", "1999-02-01T00:00:00Z", "1999-04-01T00:00:00+00:00")]
523    #[case("0 0 0 1 */3 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
524    #[case("0 0 0 1 */3 *", "2024-02-01T00:00:00Z", "2024-04-01T00:00:00+00:00")]
525    #[case("0 0 0 1 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
526    #[case("0 0 0 1 1 * *", "2024-01-01T00:00:01Z", "2025-01-01T00:00:00+00:00")]
527    #[case("0 0 0 1 1 * 1970", "2024-01-01T00:00:00Z", "None")]
528    #[case("0 0 0 1 1 * 1999", "1999-01-01T00:00:00Z", "1999-01-01T00:00:00+00:00")]
529    #[case("0 0 0 1 1 * 1999", "1999-01-01T00:00:01Z", "None")]
530    #[case("0 0 0 1 1 * 2024-2025", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
531    #[case("0 0 0 1 1 * 2024-2025", "2025-01-01T00:00:00Z", "2025-01-01T00:00:00+00:00")]
532    #[case("0 0 0 1 1 * 2024-2025", "2026-01-01T00:00:00Z", "None")]
533    #[case("0 0 0 1 1,6,12 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
534    #[case("0 0 0 1 1,6,12 *", "2024-02-01T00:00:00Z", "2024-06-01T00:00:00+00:00")]
535    #[case("0 0 0 1,15 * ?", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
536    #[case("0 0 0 1,15 * ?", "2024-01-01T00:00:01Z", "2024-01-15T00:00:00+00:00")]
537    #[case("0 0 0 1,15 * ?", "2024-01-15T00:00:01Z", "2024-02-01T00:00:00+00:00")]
538    #[case("0 0 0 1,15,L * ?", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
539    #[case("0 0 0 1,15,L * ?", "2024-01-15T00:00:01Z", "2024-01-31T00:00:00+00:00")]
540    #[case("0 0 0 1,15,L * ?", "2024-01-31T00:00:01Z", "2024-02-01T00:00:00+00:00")]
541    #[case("0 0 0 1W * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
542    #[case("0 0 0 1W * *", "2024-04-01T00:00:00Z", "2024-04-01T00:00:00+00:00")]
543    #[case("0 0 0 28-31 2 *", "2024-02-28T00:00:00Z", "2024-02-28T00:00:00+00:00")]
544    #[case("0 0 0 28-31 2 *", "2024-02-28T00:00:01Z", "2024-02-29T00:00:00+00:00")]
545    #[case("0 0 0 28-31 2 *", "2025-02-28T00:00:00Z", "2025-02-28T00:00:00+00:00")]
546    #[case("0 0 0 28-31 2 *", "2025-02-28T00:00:01Z", "2026-02-28T00:00:00+00:00")]
547    #[case("0 0 0 28,29,30,31 2 *", "2024-02-28T00:00:00Z", "2024-02-28T00:00:00+00:00")]
548    #[case("0 0 0 28,29,30,31 2 *", "2024-02-28T00:00:01Z", "2024-02-29T00:00:00+00:00")]
549    #[case("0 0 0 28,29,30,31 2 *", "2025-02-28T00:00:00Z", "2025-02-28T00:00:00+00:00")]
550    #[case("0 0 0 28,29,30,31 2 *", "2025-02-28T00:00:01Z", "2026-02-28T00:00:00+00:00")]
551    #[case("0 0 0 29 2 * 1999", "1999-01-01T00:00:00Z", "None")]
552    #[case("0 0 0 29 2 * 1999/3", "1999-01-01T00:00:00Z", "2008-02-29T00:00:00+00:00")]
553    #[case("0 0 0 29 2 *", "2024-01-01T00:00:00Z", "2024-02-29T00:00:00+00:00")]
554    #[case("0 0 0 29 2 *", "2024-03-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
555    #[case("0 0 0 29-31 2 *", "2024-02-29T00:00:00Z", "2024-02-29T00:00:00+00:00")]
556    #[case("0 0 0 29-31 2 *", "2024-02-29T00:00:01Z", "2028-02-29T00:00:00+00:00")]
557    #[case("0 0 0 29-31 2 *", "2025-02-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
558    #[case("0 0 0 29,30,31 2 *", "2024-02-29T00:00:00Z", "2024-02-29T00:00:00+00:00")]
559    #[case("0 0 0 29,30,31 2 *", "2024-02-29T00:00:01Z", "2028-02-29T00:00:00+00:00")]
560    #[case("0 0 0 29,30,31 2 *", "2025-02-01T00:00:00Z", "2028-02-29T00:00:00+00:00")]
561    #[case("0 0 0 31 */2 * 1999", "1999-01-01T00:00:00Z", "1999-01-31T00:00:00+00:00")]
562    #[case("0 0 0 31 */2 *", "2024-01-01T00:00:00Z", "2024-01-31T00:00:00+00:00")]
563    #[case("0 0 0 31 */2 *", "2024-02-01T00:00:00Z", "2024-03-31T00:00:00+00:00")]
564    #[case("0 0 0 L * * 1999", "1999-01-15T00:00:00Z", "1999-01-31T00:00:00+00:00")]
565    #[case("0 0 0 L * * 1999", "1999-02-15T00:00:00Z", "1999-02-28T00:00:00+00:00")]
566    #[case("0 0 0 L * *", "2024-01-15T00:00:00Z", "2024-01-31T00:00:00+00:00")]
567    #[case("0 0 0 L * *", "2024-02-15T00:00:00Z", "2024-02-29T00:00:00+00:00")]
568    #[case("0 0 0 L * *", "2024-04-15T00:00:00Z", "2024-04-30T00:00:00+00:00")]
569    #[case("0 0 1 1 *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00+00:00")]
570    #[case("0 0 1 1 *", "2024-01-01T00:00:01Z", "2025-01-01T00:00:00+00:00")]
571    #[case("0 0 12 ? * 2-6", "2024-01-01T00:00:00Z", "2024-01-02T12:00:00+00:00")]
572    #[case("0 0 12 ? * 2-6", "2024-01-06T12:00:00Z", "2024-01-06T12:00:00+00:00")]
573    #[case("0 0 12 ? * 2-6", "2024-01-06T12:00:01Z", "2024-01-09T12:00:00+00:00")]
574    #[case("0 0 12 * * MON-FRI 1999", "1999-01-01T00:00:00Z", "1999-01-01T12:00:00+00:00")]
575    #[case("0 0 12 * * MON-FRI 1999", "1999-01-01T12:00:01Z", "1999-01-04T12:00:00+00:00")]
576    #[case("0 0 12 * * MON-FRI", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
577    #[case("0 0 12 * * MON-FRI", "2024-01-06T00:00:00Z", "2024-01-08T12:00:00+00:00")]
578    #[case("0 0 12 1-7 * *", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
579    #[case("0 0 12 1-7 * *", "2024-01-07T12:00:00Z", "2024-01-07T12:00:00+00:00")]
580    #[case("0 0 12 1-7 * *", "2024-01-07T12:00:01Z", "2024-02-01T12:00:00+00:00")]
581    #[case("0 0 12 1,15 * ?", "2024-01-01T00:00:00Z", "2024-01-01T12:00:00+00:00")]
582    #[case("0 0 12 1,15 * ?", "2024-01-01T12:00:01Z", "2024-01-15T12:00:00+00:00")]
583    #[case("0 0 6 * * 1-5", "2024-01-01T00:00:00Z", "2024-01-01T06:00:00+00:00")]
584    #[case("0 0 6 * * 1-5", "2024-01-06T00:00:00Z", "2024-01-08T06:00:00+00:00")]
585    #[case("0 0 9 * * 1", "2024-01-01T00:00:00Z", "2024-01-01T09:00:00+00:00")]
586    #[case("0 0 9 * * 1", "2024-01-01T09:00:01Z", "2024-01-08T09:00:00+00:00")]
587    #[case("0 0 9 * * 1#1", "2024-04-12T00:00:00Z", "2024-05-06T09:00:00+00:00")]
588    #[case("0 0 9 * * 6#4", "2024-11-30T09:00:00Z", "2024-12-28T09:00:00+00:00")]
589    #[case("0 0 9-17 * * 1-5", "2024-01-01T08:00:00Z", "2024-01-01T09:00:00+00:00")]
590    #[case("0 0 9-17 * * 1-5", "2024-01-01T17:00:01Z", "2024-01-02T09:00:00+00:00")]
591    #[case("0 15,45 9-17 * * 1-5", "2024-01-01T09:00:00Z", "2024-01-01T09:15:00+00:00")]
592    #[case("0 15,45 9-17 * * 1-5", "2024-01-01T09:15:01Z", "2024-01-01T09:45:00+00:00")]
593    #[case("0 30 0 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:30:00+00:00")]
594    #[case("30 0 0 1 * *", "2024-01-01T00:00:00Z", "2024-01-01T00:00:30+00:00")]
595    #[case("30 0 0 1 * *", "2024-01-01T00:00:30Z", "2024-01-01T00:00:30+00:00")]
596    #[case("30 0 0 1 * *", "2024-01-01T00:00:30.001Z", "2024-02-01T00:00:30+00:00")]
597    #[case("25 * * * *", "2024-01-01T00:21:21Z", "2024-01-01T00:25:00+00:00")]
598    #[case("1 2 29-31 * *", "2024-01-01T00:00:21Z", "2024-01-29T02:01:00+00:00")]
599    #[case("1 2 29-31 * *", "2024-01-31T00:00:21Z", "2024-01-31T02:01:00+00:00")]
600    #[case("1 2 29-31 * *", "2024-02-01T00:00:21Z", "2024-02-29T02:01:00+00:00")]
601    #[case("1 2 29-31 * *", "2024-03-31T00:00:21Z", "2024-03-31T02:01:00+00:00")]
602    #[case("1 2 29-31 * *", "2025-01-01T00:00:21Z", "2025-01-29T02:01:00+00:00")]
603    #[case("1 2 29-31 * *", "2025-02-01T00:00:21Z", "2025-03-29T02:01:00+00:00")]
604    #[case("1 2 29-31 * *", "2025-03-31T00:00:21Z", "2025-03-31T02:01:00+00:00")]
605    #[case("@yearly", "2025-03-31T00:00:21Z", "2026-01-01T00:00:00+00:00")]
606    #[case("@annually", "2025-03-31T00:00:21Z", "2026-01-01T00:00:00+00:00")]
607    #[case("@monthly", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
608    #[case("@weekly", "2025-03-31T00:00:21Z", "2025-04-06T00:00:00+00:00")]
609    #[case("@daily", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
610    #[case("@midnight", "2025-03-31T00:00:21Z", "2025-04-01T00:00:00+00:00")]
611    #[case("@hourly", "2025-03-31T00:00:21Z", "2025-03-31T01:00:00+00:00")]
612    #[timeout(Duration::from_secs(1))]
613    fn test_schedule_upcoming(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
614        let schedule = Schedule::new(pattern).unwrap();
615        let current = DateTime::parse_from_rfc3339(current).unwrap();
616        let next = schedule.upcoming(&current);
617
618        if expected == "None" {
619            assert!(
620                next.is_none(),
621                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
622            );
623        } else {
624            assert!(
625                next.is_some(),
626                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
627            );
628
629            assert_eq!(
630                next.unwrap().to_rfc3339(),
631                expected,
632                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
633            );
634        }
635    }
636
637    #[test]
638    fn test_inc_year() {
639        let mut year = Some(2024);
640        let mut month = Some(1);
641        let mut dom = Some(1);
642        let mut hour = Some(0);
643        let mut minute = Some(0);
644        let mut second = Some(0);
645
646        assert_eq!(
647            inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
648            Some(2025)
649        );
650        assert_eq!(year, Some(2025));
651        assert_eq!(month, Some(1));
652        assert_eq!(dom, Some(1));
653        assert_eq!(hour, Some(0));
654        assert_eq!(minute, Some(0));
655        assert_eq!(second, Some(0));
656
657        year = Some(MAX_YEAR);
658        assert_eq!(
659            inc_year(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
660            None
661        );
662        assert_eq!(year, None);
663    }
664
665    #[test]
666    fn test_inc_month() {
667        let mut year = Some(2024);
668        let mut month = Some(1);
669        let mut dom = Some(1);
670        let mut hour = Some(0);
671        let mut minute = Some(0);
672        let mut second = Some(0);
673
674        assert_eq!(
675            inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
676            Some(2)
677        );
678        assert_eq!(year, Some(2024));
679        assert_eq!(month, Some(2));
680        assert_eq!(dom, Some(1));
681        assert_eq!(hour, Some(0));
682        assert_eq!(minute, Some(0));
683        assert_eq!(second, Some(0));
684
685        year = Some(2024);
686        month = Some(12);
687        assert_eq!(
688            inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
689            Some(1)
690        );
691        assert_eq!(year, Some(2025));
692        assert_eq!(month, Some(1));
693        assert_eq!(dom, Some(1));
694        assert_eq!(hour, Some(0));
695        assert_eq!(minute, Some(0));
696        assert_eq!(second, Some(0));
697
698        year = Some(MAX_YEAR);
699        month = Some(12);
700        assert_eq!(
701            inc_month(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
702            None
703        );
704        assert_eq!(year, None);
705    }
706
707    #[test]
708    fn test_inc_dom() {
709        let mut year = Some(2024);
710        let mut month = Some(1);
711        let mut dom = Some(1);
712        let mut hour = Some(0);
713        let mut minute = Some(0);
714        let mut second = Some(0);
715
716        assert_eq!(
717            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
718            Some(2)
719        );
720        assert_eq!(year, Some(2024));
721        assert_eq!(month, Some(1));
722        assert_eq!(dom, Some(2));
723        assert_eq!(hour, Some(0));
724        assert_eq!(minute, Some(0));
725        assert_eq!(second, Some(0));
726
727        year = Some(2024);
728        month = Some(1);
729        dom = Some(31);
730        assert_eq!(
731            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
732            Some(1)
733        );
734        assert_eq!(year, Some(2024));
735        assert_eq!(month, Some(2));
736        assert_eq!(dom, Some(1));
737        assert_eq!(hour, Some(0));
738        assert_eq!(minute, Some(0));
739        assert_eq!(second, Some(0));
740
741        year = Some(2024);
742        month = Some(12);
743        dom = Some(31);
744        assert_eq!(
745            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
746            Some(1)
747        );
748        assert_eq!(year, Some(2025));
749        assert_eq!(month, Some(1));
750        assert_eq!(dom, Some(1));
751        assert_eq!(hour, Some(0));
752        assert_eq!(minute, Some(0));
753        assert_eq!(second, Some(0));
754
755        year = Some(2024);
756        month = Some(2);
757        dom = Some(28);
758        assert_eq!(
759            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
760            Some(29)
761        );
762        assert_eq!(year, Some(2024));
763        assert_eq!(month, Some(2));
764        assert_eq!(dom, Some(29));
765        assert_eq!(hour, Some(0));
766        assert_eq!(minute, Some(0));
767        assert_eq!(second, Some(0));
768
769        year = Some(2025);
770        month = Some(2);
771        dom = Some(28);
772        assert_eq!(
773            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
774            Some(1)
775        );
776        assert_eq!(year, Some(2025));
777        assert_eq!(month, Some(3));
778        assert_eq!(dom, Some(1));
779        assert_eq!(hour, Some(0));
780        assert_eq!(minute, Some(0));
781        assert_eq!(second, Some(0));
782
783        year = Some(MAX_YEAR);
784        month = Some(12);
785        dom = Some(31);
786        assert_eq!(
787            inc_dom(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
788            None
789        );
790        assert_eq!(year, None);
791    }
792
793    #[test]
794    fn test_inc_hour() {
795        let mut year = Some(2024);
796        let mut month = Some(1);
797        let mut dom = Some(1);
798        let mut hour = Some(0);
799        let mut minute = Some(0);
800        let mut second = Some(0);
801
802        assert_eq!(
803            inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
804            Some(1)
805        );
806        assert_eq!(year, Some(2024));
807        assert_eq!(month, Some(1));
808        assert_eq!(dom, Some(1));
809        assert_eq!(hour, Some(1));
810        assert_eq!(minute, Some(0));
811        assert_eq!(second, Some(0));
812
813        year = Some(2024);
814        month = Some(1);
815        dom = Some(1);
816        hour = Some(23);
817        assert_eq!(
818            inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
819            Some(0)
820        );
821        assert_eq!(year, Some(2024));
822        assert_eq!(month, Some(1));
823        assert_eq!(dom, Some(2));
824        assert_eq!(hour, Some(0));
825        assert_eq!(minute, Some(0));
826        assert_eq!(second, Some(0));
827
828        year = Some(2024);
829        month = Some(12);
830        dom = Some(31);
831        hour = Some(23);
832        assert_eq!(
833            inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
834            Some(0)
835        );
836        assert_eq!(year, Some(2025));
837        assert_eq!(month, Some(1));
838        assert_eq!(dom, Some(1));
839        assert_eq!(hour, Some(0));
840        assert_eq!(minute, Some(0));
841        assert_eq!(second, Some(0));
842
843        year = Some(MAX_YEAR);
844        month = Some(12);
845        dom = Some(31);
846        hour = Some(23);
847        assert_eq!(
848            inc_hour(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
849            None
850        );
851        assert_eq!(year, None);
852    }
853
854    #[test]
855    fn test_inc_minute() {
856        let mut year = Some(2024);
857        let mut month = Some(1);
858        let mut dom = Some(1);
859        let mut hour = Some(0);
860        let mut minute = Some(0);
861        let mut second = Some(0);
862
863        assert_eq!(
864            inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
865            Some(1)
866        );
867        assert_eq!(year, Some(2024));
868        assert_eq!(month, Some(1));
869        assert_eq!(dom, Some(1));
870        assert_eq!(hour, Some(0));
871        assert_eq!(minute, Some(1));
872        assert_eq!(second, Some(0));
873
874        year = Some(2024);
875        month = Some(1);
876        dom = Some(1);
877        hour = Some(23);
878        minute = Some(59);
879        assert_eq!(
880            inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
881            Some(0)
882        );
883        assert_eq!(year, Some(2024));
884        assert_eq!(month, Some(1));
885        assert_eq!(dom, Some(2));
886        assert_eq!(hour, Some(0));
887        assert_eq!(minute, Some(0));
888        assert_eq!(second, Some(0));
889
890        year = Some(2024);
891        month = Some(12);
892        dom = Some(31);
893        hour = Some(23);
894        minute = Some(59);
895        assert_eq!(
896            inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
897            Some(0)
898        );
899        assert_eq!(year, Some(2025));
900        assert_eq!(month, Some(1));
901        assert_eq!(dom, Some(1));
902        assert_eq!(hour, Some(0));
903        assert_eq!(minute, Some(0));
904        assert_eq!(second, Some(0));
905
906        year = Some(MAX_YEAR);
907        month = Some(12);
908        dom = Some(31);
909        hour = Some(23);
910        minute = Some(59);
911        assert_eq!(
912            inc_minute(&mut year, &mut month, &mut dom, &mut hour, &mut minute, &mut second),
913            None
914        );
915        assert_eq!(year, None);
916    }
917
918    #[template]
919    #[rstest]
920    #[case("* * * * * * *", "* * * * * * *")]
921    #[case("* * * * * *", "* * * * * * *")]
922    #[case("* * * * *", "0 * * * * * *")]
923    #[case("*/5 * * * *", "0 0/5 * * * * *")]
924    #[case("0 */15 */6 * * *", "0 0/15 0/6 * * * *")]
925    #[case("0 0 ? 1 0", "0 0 0 ? 1 0 *")]
926    #[case("0 0 * * SUN", "0 0 0 * * 0 *")]
927    #[case("0 0 * 1 0", "0 0 0 * 1 0 *")]
928    #[case("0 0 1 1 ?", "0 0 0 1 1 ? *")]
929    #[case("0 0 1 1 *", "0 0 0 1 1 * *")]
930    #[case("0 0 12 * * MON", "0 0 12 * * 1 *")]
931    #[case("0 0 22 * * 1-5", "0 0 22 * * 1-5 *")]
932    #[case("0 0/5 14,18 * * *", "0 0/5 14,18 * * * *")]
933    #[case("0 15 10 ? * MON-FRI", "0 15 10 ? * 1-5 *")]
934    #[case("1,22,45 5/2 0-15 1-6/2 */6 * 2000", "1,22,45 5/2 0-15 1-6/2 1/6 * 2000")]
935    #[case("23 0-20/2 * * *", "0 23 0-20/2 * * * *")]
936    #[case("30 0 1 1 * *", "30 0 1 1 * * *")]
937    #[case("5,10,15,20 * * * *", "0 5,10,15,20 * * * * *")]
938    #[case("@yearly", "0 0 0 1 1 ? *")]
939    #[case("@annually", "0 0 0 1 1 ? *")]
940    #[case("@monthly", "0 0 0 1 * ? *")]
941    #[case("@weekly", "0 0 0 ? * 0 *")]
942    #[case("@daily", "0 0 0 * * * *")]
943    #[case("@midnight", "0 0 0 * * * *")]
944    #[case("@hourly", "0 0 * * * * *")]
945    fn valid_schedules_to_test(#[case] input: &str) {}
946
947    #[apply(valid_schedules_to_test)]
948    fn test_schedule_display_and_new(#[case] input: &str, #[case] expected: &str) {
949        assert_eq!(Schedule::new(input).unwrap().to_string(), expected);
950    }
951
952    #[apply(valid_schedules_to_test)]
953    fn test_try_from_string(#[case] input: &str, #[case] _expected: &str) {
954        // &str
955        let schedule1 = Schedule::new(input).unwrap();
956        let schedule2 = Schedule::try_from(input).unwrap();
957        assert_eq!(schedule1, schedule2);
958
959        // &String
960        let tst_string = String::from(input);
961        let schedule2 = Schedule::try_from(&tst_string).unwrap();
962        assert_eq!(schedule1, schedule2);
963
964        // String
965        let schedule2 = Schedule::try_from(tst_string).unwrap();
966        assert_eq!(schedule1, schedule2);
967
968        // from_str
969        let schedule2 = Schedule::from_str(input).unwrap();
970        assert_eq!(schedule1, schedule2);
971    }
972
973    #[rstest]
974    #[timeout(Duration::from_secs(1))]
975    fn test_schedule_iter() {
976        let schedule = Schedule::new("0 0 12 * 1 MON 2024").unwrap();
977        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
978
979        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T12:00:00+00:00");
980        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-08T12:00:00+00:00");
981        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T12:00:00+00:00");
982        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-22T12:00:00+00:00");
983        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-29T12:00:00+00:00");
984        assert_eq!(iter.next(), None);
985    }
986
987    #[rstest]
988    #[timeout(Duration::from_secs(1))]
989    fn test_schedule_iter_every_second() {
990        let schedule = Schedule::new("* * * * * *").unwrap();
991        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
992
993        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:01+00:00");
994        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:02+00:00");
995        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:03+00:00");
996        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:04+00:00");
997        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:00:05+00:00");
998    }
999
1000    #[rstest]
1001    #[timeout(Duration::from_secs(1))]
1002    fn test_schedule_iter_every_minute() {
1003        let schedule = Schedule::new("* * * * *").unwrap();
1004        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1005
1006        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:01:00+00:00");
1007        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:02:00+00:00");
1008        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:03:00+00:00");
1009        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:04:00+00:00");
1010        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T00:05:00+00:00");
1011    }
1012
1013    #[rstest]
1014    #[timeout(Duration::from_secs(1))]
1015    fn test_schedule_iter_every_hour() {
1016        let schedule = Schedule::new("13 * * * *").unwrap();
1017        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T07:01:01+00:00").unwrap());
1018
1019        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T07:13:00+00:00");
1020        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T08:13:00+00:00");
1021        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T09:13:00+00:00");
1022        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T10:13:00+00:00");
1023        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T11:13:00+00:00");
1024    }
1025
1026    #[rstest]
1027    #[timeout(Duration::from_secs(1))]
1028    fn test_schedule_iter_every_day() {
1029        let schedule = Schedule::new("22 5 * * *").unwrap();
1030        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T04:01:01+00:00").unwrap());
1031
1032        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T05:22:00+00:00");
1033        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-02T05:22:00+00:00");
1034        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-03T05:22:00+00:00");
1035        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-04T05:22:00+00:00");
1036        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-05T05:22:00+00:00");
1037    }
1038
1039    #[rstest]
1040    #[timeout(Duration::from_secs(1))]
1041    fn test_schedule_iter_every_month() {
1042        let schedule = Schedule::new("13 13 12 * *").unwrap();
1043        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1044
1045        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-02-12T13:13:00+00:00");
1046        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-03-12T13:13:00+00:00");
1047        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-04-12T13:13:00+00:00");
1048        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-05-12T13:13:00+00:00");
1049        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-06-12T13:13:00+00:00");
1050    }
1051
1052    #[rstest]
1053    #[timeout(Duration::from_secs(1))]
1054    fn test_schedule_iter_every_weekday() {
1055        let schedule = Schedule::new("13 13 ? * *").unwrap();
1056        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1057
1058        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-13T13:13:00+00:00");
1059        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-14T13:13:00+00:00");
1060        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T13:13:00+00:00");
1061        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-16T13:13:00+00:00");
1062        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-17T13:13:00+00:00");
1063    }
1064
1065    #[rstest]
1066    #[timeout(Duration::from_secs(1))]
1067    fn test_schedule_iter_every_year() {
1068        let schedule = Schedule::new("30 12 22 6 ?").unwrap();
1069        let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2021-01-12T13:13:01+00:00").unwrap());
1070
1071        assert_eq!(iter.next().unwrap().to_rfc3339(), "2021-06-22T12:30:00+00:00");
1072        assert_eq!(iter.next().unwrap().to_rfc3339(), "2022-06-22T12:30:00+00:00");
1073        assert_eq!(iter.next().unwrap().to_rfc3339(), "2023-06-22T12:30:00+00:00");
1074        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-06-22T12:30:00+00:00");
1075        assert_eq!(iter.next().unwrap().to_rfc3339(), "2025-06-22T12:30:00+00:00");
1076    }
1077
1078    #[rstest]
1079    #[timeout(Duration::from_secs(1))]
1080    fn test_schedule_into_iter() {
1081        let schedule = Schedule::new("0 0 12 * 1 MON 2024").unwrap();
1082        let mut iter = schedule.into_iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
1083
1084        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-01T12:00:00+00:00");
1085        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-08T12:00:00+00:00");
1086        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-15T12:00:00+00:00");
1087        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-22T12:00:00+00:00");
1088        assert_eq!(iter.next().unwrap().to_rfc3339(), "2024-01-29T12:00:00+00:00");
1089        assert_eq!(iter.next(), None);
1090    }
1091
1092    #[template]
1093    #[rstest]
1094    #[case("* * * *")]
1095    #[case("* * ? * ?")]
1096    #[case("* * 10 * 1")]
1097    #[case("* * * * 2/2")]
1098    #[case("0 1 2 3 * * 1969")]
1099    #[case("0 0 0 ? * 6-1")]
1100    #[case("@minutely")]
1101    fn invalid_schedules_to_test(#[case] input: &str) {}
1102
1103    #[apply(invalid_schedules_to_test)]
1104    fn test_invalid_schedule_constructor(#[case] input: &str) {
1105        assert!(Schedule::new(input).is_err(), "input = {input}");
1106    }
1107
1108    #[apply(invalid_schedules_to_test)]
1109    fn test_try_from_invalid_string(#[case] input: &str) {
1110        assert!(Schedule::try_from(input).is_err(), "input = {input}");
1111        assert!(Schedule::from_str(input).is_err(), "input = {input}");
1112    }
1113
1114    #[rstest]
1115    // leap year
1116    #[case("0 0 0 29 2 ? 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1117    #[case("0 0 0 29 2 ? 2024-2099", "2024-03-01T23:59:59Z", "2028-02-29T00:00:00+00:00")]
1118    #[case("0 0 0 29 2 ? 2025-2099", "2024-02-01T23:59:59Z", "2028-02-29T00:00:00+00:00")]
1119    #[case("0 0 0 29 2 ? 1971/7", "1970-02-01T23:59:59Z", "1992-02-29T00:00:00+00:00")]
1120    #[case("0 0 0 29 2 ? 2024-2027", "2024-02-29T00:00:01Z", "None")]
1121    // end of month rollover
1122    #[case("0 0 0 1 * ? 2024", "2024-01-31T23:59:59Z", "2024-02-01T00:00:00+00:00")]
1123    #[case("0 0 0 1 * ? 2024", "2024-02-28T23:59:59Z", "2024-03-01T00:00:00+00:00")]
1124    #[case("0 0 0 1 * ? 2024", "2024-02-29T23:59:59Z", "2024-03-01T00:00:00+00:00")]
1125    #[case("0 0 0 1 * ? 2024", "2024-03-31T23:59:59Z", "2024-04-01T00:00:00+00:00")]
1126    #[case("0 0 0 1 * ? 2024", "2024-04-30T23:59:59Z", "2024-05-01T00:00:00+00:00")]
1127    #[case("0 0 0 1 * ? 2024", "2024-11-30T23:59:59Z", "2024-12-01T00:00:00+00:00")]
1128    // end-of-year rollover
1129    #[case("0 0 0 1 1 ? 2025", "2024-12-31T23:59:59Z", "2025-01-01T00:00:00+00:00")]
1130    // last day of month
1131    #[case("0 0 0 L 2 ? 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1132    #[case("0 0 0 L 2 ? 2025", "2024-02-28T23:59:59Z", "2025-02-28T00:00:00+00:00")]
1133    // last weekday of month
1134    #[case("0 0 0 ? 2 4L 2024", "2024-02-28T23:59:59Z", "2024-02-29T00:00:00+00:00")]
1135    // weekday nearest to specific day
1136    #[case("0 0 0 15W * ? 2024", "2024-01-14T23:59:59Z", "2024-01-15T00:00:00+00:00")]
1137    // nth day of the week
1138    #[case("0 0 0 ? * 1#3 2024", "2024-01-20T23:59:59Z", "2024-02-19T00:00:00+00:00")]
1139    #[timeout(Duration::from_secs(1))]
1140    fn test_edge_cases(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
1141        let schedule = Schedule::new(pattern).unwrap();
1142        let current = DateTime::parse_from_rfc3339(current).unwrap();
1143        let next = schedule.upcoming(&current);
1144
1145        if expected == "None" {
1146            assert!(
1147                next.is_none(),
1148                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1149            );
1150        } else {
1151            assert!(
1152                next.is_some(),
1153                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1154            );
1155
1156            assert_eq!(
1157                next.unwrap().to_rfc3339(),
1158                expected,
1159                "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1160            );
1161        }
1162    }
1163
1164    #[apply(valid_schedules_to_test)]
1165    fn test_schedule_to_string(#[case] input: &str, #[case] expected: &str) {
1166        let schedule = Schedule::new(input).unwrap();
1167
1168        let string: String = (&schedule).into();
1169        assert_eq!(string, expected);
1170
1171        let string: String = schedule.into();
1172        assert_eq!(string, expected);
1173    }
1174
1175    #[cfg(feature = "tz")]
1176    mod tz {
1177        use super::super::*;
1178        use chrono::{Local, Utc};
1179        use rstest::rstest;
1180        use rstest_reuse::{apply, template};
1181        use std::{fmt::Debug, time::Duration};
1182
1183        #[template]
1184        #[rstest]
1185        #[case("TZ=Europe/Kyiv * * * * * * *", "TZ=Europe/Kyiv * * * * * * *")]
1186        #[case("TZ=Europe/London * * * * * *", "TZ=Europe/London * * * * * * *")]
1187        #[case("TZ=UTC * * * * *", "TZ=UTC 0 * * * * * *")]
1188        #[case("TZ=US/Pacific */5 * * * *", "TZ=US/Pacific 0 0/5 * * * * *")]
1189        #[case("TZ=EET 0 */15 */6 * * *", "TZ=EET 0 0/15 0/6 * * * *")]
1190        #[case("TZ=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1191        #[case("Tz=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1192        #[case("tz=Asia/Tokyo @yearly", "TZ=Asia/Tokyo 0 0 0 1 1 ? *")]
1193        #[case("tz=Europe/Paris @yearly", "TZ=Europe/Paris 0 0 0 1 1 ? *")]
1194        fn valid_schedules_to_test(#[case] input: &str, #[case] expected: &str) {}
1195
1196        #[apply(valid_schedules_to_test)]
1197        fn test_schedule_display_and_new(#[case] input: &str, #[case] expected: &str) {
1198            assert_eq!(Schedule::new(input).unwrap().to_string(), expected);
1199        }
1200
1201        #[apply(valid_schedules_to_test)]
1202        fn test_try_from_string(#[case] input: &str, #[case] _expected: &str) {
1203            // &str
1204            let schedule1 = Schedule::new(input).unwrap();
1205            let schedule2 = Schedule::try_from(input).unwrap();
1206            assert_eq!(schedule1, schedule2);
1207
1208            // &String
1209            let tst_string = String::from(input);
1210            let schedule2 = Schedule::try_from(&tst_string).unwrap();
1211            assert_eq!(schedule1, schedule2);
1212
1213            // String
1214            let schedule2 = Schedule::try_from(tst_string).unwrap();
1215            assert_eq!(schedule1, schedule2);
1216
1217            // from_str
1218            let schedule2 = Schedule::from_str(input).unwrap();
1219            assert_eq!(schedule1, schedule2);
1220        }
1221
1222        #[template]
1223        #[rstest]
1224        #[case("TZ * * * *")]
1225        #[case("TZ= * * ? * ?")]
1226        #[case("tz=UTC * * 10 * 1")]
1227        #[case("TZ=Aaa/Bbb * * * * 2/2")]
1228        #[case("TZ=Aaa/Bbb * * * * *")]
1229        #[case("TZ =UTC * * * * *")]
1230        #[case("TZ= UTC * * * * *")]
1231        #[case("TZ = UTC * * * * *")]
1232        #[case("TZ= 0 0 0 ? * 1-6")]
1233        #[case("tz= @hourly")]
1234        fn invalid_schedules_to_test(#[case] input: &str) {}
1235
1236        #[apply(invalid_schedules_to_test)]
1237        fn test_invalid_schedule_constructor(#[case] input: &str) {
1238            assert!(Schedule::new(input).is_err(), "input = {input}");
1239        }
1240
1241        #[apply(invalid_schedules_to_test)]
1242        fn test_try_from_invalid_string(#[case] input: &str) {
1243            assert!(Schedule::try_from(input).is_err(), "input = {input}");
1244            assert!(Schedule::from_str(input).is_err(), "input = {input}");
1245        }
1246
1247        #[rstest]
1248        #[case("TZ=Europe/Kyiv @monthly", "2025-03-31T00:00:21Z", "2025-03-31T21:00:00+00:00")]
1249        #[case("TZ=Europe/Kyiv @monthly", "2025-03-31T00:00:21+02:00", "2025-03-31T23:00:00+02:00")]
1250        #[case("TZ=Europe/Kyiv @monthly", "2025-11-30T00:00:21Z", "2025-11-30T22:00:00+00:00")]
1251        #[case("TZ=EET @hourly", "2000-03-25T23:00:01Z", "2000-03-26T00:00:00+00:00")]
1252        #[case("TZ=EET @hourly", "2000-03-26T00:00:01Z", "2000-03-26T01:00:00+00:00")]
1253        #[case("TZ=EET @hourly", "2000-03-26T01:00:01Z", "2000-03-26T02:00:00+00:00")]
1254        #[case("TZ=EET @hourly", "2000-10-28T22:00:01Z", "2000-10-28T23:00:00+00:00")]
1255        #[case("TZ=EET @hourly", "2000-10-28T23:00:01Z", "2000-10-29T00:00:00+00:00")] // 8
1256        #[case("TZ=EET @hourly", "2000-10-29T00:00:01Z", "2000-10-29T02:00:00+00:00")] // 9
1257        #[case("TZ=EET @hourly", "2000-10-29T01:00:01Z", "2000-10-29T02:00:00+00:00")] // 10
1258        #[case("TZ=EET @hourly", "2000-10-29T02:00:01Z", "2000-10-29T03:00:00+00:00")]
1259        #[timeout(Duration::from_secs(1))]
1260        fn test_schedule_upcoming(#[case] pattern: &str, #[case] current: &str, #[case] expected: &str) {
1261            let schedule = Schedule::new(pattern).unwrap();
1262            let current = DateTime::parse_from_rfc3339(current).unwrap();
1263            let next = schedule.upcoming(&current);
1264
1265            if expected == "None" {
1266                assert!(
1267                    next.is_none(),
1268                    "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1269                );
1270            } else {
1271                assert!(
1272                    next.is_some(),
1273                    "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1274                );
1275
1276                assert_eq!(
1277                    next.unwrap().to_rfc3339(),
1278                    expected,
1279                    "pattern = {pattern}, schedule = {schedule:?}, current = {current}, next = {next:?}"
1280                );
1281            }
1282        }
1283
1284        #[rstest]
1285        #[timeout(Duration::from_secs(1))]
1286        fn test_schedule_iter() {
1287            let schedule = Schedule::new("TZ=UTC 0 0 12 * 1 MON 2024").unwrap();
1288            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:00+00:00").unwrap());
1289
1290            assert_eq!(
1291                iter.next().unwrap().to_rfc3339(),
1292                "2024-01-01T12:00:00+00:00",
1293                "schedule = {schedule}"
1294            );
1295            assert_eq!(
1296                iter.next().unwrap().to_rfc3339(),
1297                "2024-01-08T12:00:00+00:00",
1298                "schedule = {schedule}"
1299            );
1300            assert_eq!(
1301                iter.next().unwrap().to_rfc3339(),
1302                "2024-01-15T12:00:00+00:00",
1303                "schedule = {schedule}"
1304            );
1305            assert_eq!(
1306                iter.next().unwrap().to_rfc3339(),
1307                "2024-01-22T12:00:00+00:00",
1308                "schedule = {schedule}"
1309            );
1310            assert_eq!(
1311                iter.next().unwrap().to_rfc3339(),
1312                "2024-01-29T12:00:00+00:00",
1313                "schedule = {schedule}"
1314            );
1315            assert_eq!(iter.next(), None);
1316        }
1317
1318        #[rstest]
1319        #[timeout(Duration::from_secs(1))]
1320        fn test_schedule_iter_every_second() {
1321            let schedule = Schedule::new("TZ=EET * * * * * *").unwrap();
1322            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1323
1324            assert_eq!(
1325                iter.next().unwrap().to_rfc3339(),
1326                "2024-01-01T00:00:01+00:00",
1327                "schedule = {schedule}"
1328            );
1329            assert_eq!(
1330                iter.next().unwrap().to_rfc3339(),
1331                "2024-01-01T00:00:02+00:00",
1332                "schedule = {schedule}"
1333            );
1334            assert_eq!(
1335                iter.next().unwrap().to_rfc3339(),
1336                "2024-01-01T00:00:03+00:00",
1337                "schedule = {schedule}"
1338            );
1339            assert_eq!(
1340                iter.next().unwrap().to_rfc3339(),
1341                "2024-01-01T00:00:04+00:00",
1342                "schedule = {schedule}"
1343            );
1344            assert_eq!(
1345                iter.next().unwrap().to_rfc3339(),
1346                "2024-01-01T00:00:05+00:00",
1347                "schedule = {schedule}"
1348            );
1349        }
1350
1351        #[rstest]
1352        #[timeout(Duration::from_secs(1))]
1353        fn test_schedule_iter_every_minute() {
1354            let schedule = Schedule::new("TZ=Europe/Kyiv * * * * *").unwrap();
1355            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T00:00:01+00:00").unwrap());
1356
1357            assert_eq!(
1358                iter.next().unwrap().to_rfc3339(),
1359                "2024-01-01T00:01:00+00:00",
1360                "schedule = {schedule}"
1361            );
1362            assert_eq!(
1363                iter.next().unwrap().to_rfc3339(),
1364                "2024-01-01T00:02:00+00:00",
1365                "schedule = {schedule}"
1366            );
1367            assert_eq!(
1368                iter.next().unwrap().to_rfc3339(),
1369                "2024-01-01T00:03:00+00:00",
1370                "schedule = {schedule}"
1371            );
1372            assert_eq!(
1373                iter.next().unwrap().to_rfc3339(),
1374                "2024-01-01T00:04:00+00:00",
1375                "schedule = {schedule}"
1376            );
1377            assert_eq!(
1378                iter.next().unwrap().to_rfc3339(),
1379                "2024-01-01T00:05:00+00:00",
1380                "schedule = {schedule}"
1381            );
1382        }
1383
1384        #[rstest]
1385        #[timeout(Duration::from_secs(1))]
1386        fn test_schedule_iter_every_hour() {
1387            let schedule = Schedule::new("TZ=Canada/Eastern 13 * * * *").unwrap();
1388            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T07:01:01+00:00").unwrap());
1389
1390            assert_eq!(
1391                iter.next().unwrap().to_rfc3339(),
1392                "2024-01-01T07:13:00+00:00",
1393                "schedule = {schedule}"
1394            );
1395            assert_eq!(
1396                iter.next().unwrap().to_rfc3339(),
1397                "2024-01-01T08:13:00+00:00",
1398                "schedule = {schedule}"
1399            );
1400            assert_eq!(
1401                iter.next().unwrap().to_rfc3339(),
1402                "2024-01-01T09:13:00+00:00",
1403                "schedule = {schedule}"
1404            );
1405            assert_eq!(
1406                iter.next().unwrap().to_rfc3339(),
1407                "2024-01-01T10:13:00+00:00",
1408                "schedule = {schedule}"
1409            );
1410            assert_eq!(
1411                iter.next().unwrap().to_rfc3339(),
1412                "2024-01-01T11:13:00+00:00",
1413                "schedule = {schedule}"
1414            );
1415        }
1416
1417        #[rstest]
1418        #[timeout(Duration::from_secs(1))]
1419        fn test_schedule_iter_every_day() {
1420            let schedule = Schedule::new("TZ=Asia/Tokyo 22 5 * * *").unwrap();
1421            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-01T04:01:01+00:00").unwrap());
1422
1423            assert_eq!(
1424                iter.next().unwrap().to_rfc3339(),
1425                "2024-01-01T20:22:00+00:00",
1426                "schedule = {schedule}"
1427            );
1428            assert_eq!(
1429                iter.next().unwrap().to_rfc3339(),
1430                "2024-01-02T20:22:00+00:00",
1431                "schedule = {schedule}"
1432            );
1433            assert_eq!(
1434                iter.next().unwrap().to_rfc3339(),
1435                "2024-01-03T20:22:00+00:00",
1436                "schedule = {schedule}"
1437            );
1438            assert_eq!(
1439                iter.next().unwrap().to_rfc3339(),
1440                "2024-01-04T20:22:00+00:00",
1441                "schedule = {schedule}"
1442            );
1443            assert_eq!(
1444                iter.next().unwrap().to_rfc3339(),
1445                "2024-01-05T20:22:00+00:00",
1446                "schedule = {schedule}"
1447            );
1448        }
1449
1450        #[rstest]
1451        #[timeout(Duration::from_secs(1))]
1452        fn test_schedule_iter_every_month() {
1453            let schedule = Schedule::new("TZ=GMT 13 13 12 * *").unwrap();
1454            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1455
1456            assert_eq!(
1457                iter.next().unwrap().to_rfc3339(),
1458                "2024-02-12T13:13:00+00:00",
1459                "schedule = {schedule}"
1460            );
1461            assert_eq!(
1462                iter.next().unwrap().to_rfc3339(),
1463                "2024-03-12T13:13:00+00:00",
1464                "schedule = {schedule}"
1465            );
1466            assert_eq!(
1467                iter.next().unwrap().to_rfc3339(),
1468                "2024-04-12T13:13:00+00:00",
1469                "schedule = {schedule}"
1470            );
1471            assert_eq!(
1472                iter.next().unwrap().to_rfc3339(),
1473                "2024-05-12T13:13:00+00:00",
1474                "schedule = {schedule}"
1475            );
1476            assert_eq!(
1477                iter.next().unwrap().to_rfc3339(),
1478                "2024-06-12T13:13:00+00:00",
1479                "schedule = {schedule}"
1480            );
1481        }
1482
1483        #[rstest]
1484        #[timeout(Duration::from_secs(1))]
1485        fn test_schedule_iter_every_weekday() {
1486            let schedule = Schedule::new("TZ=Antarctica/South_Pole 13 13 ? * *").unwrap();
1487            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2024-01-12T13:13:01+00:00").unwrap());
1488
1489            assert_eq!(
1490                iter.next().unwrap().to_rfc3339(),
1491                "2024-01-13T00:13:00+00:00",
1492                "schedule = {schedule}"
1493            );
1494            assert_eq!(
1495                iter.next().unwrap().to_rfc3339(),
1496                "2024-01-14T00:13:00+00:00",
1497                "schedule = {schedule}"
1498            );
1499            assert_eq!(
1500                iter.next().unwrap().to_rfc3339(),
1501                "2024-01-15T00:13:00+00:00",
1502                "schedule = {schedule}"
1503            );
1504            assert_eq!(
1505                iter.next().unwrap().to_rfc3339(),
1506                "2024-01-16T00:13:00+00:00",
1507                "schedule = {schedule}"
1508            );
1509            assert_eq!(
1510                iter.next().unwrap().to_rfc3339(),
1511                "2024-01-17T00:13:00+00:00",
1512                "schedule = {schedule}"
1513            );
1514        }
1515
1516        #[rstest]
1517        #[timeout(Duration::from_secs(1))]
1518        fn test_schedule_iter_every_year() {
1519            let schedule = Schedule::new("TZ=Asia/Shanghai 30 12 22 6 ?").unwrap();
1520            let mut iter = schedule.iter(&DateTime::parse_from_rfc3339("2021-01-12T13:13:01+00:00").unwrap());
1521
1522            assert_eq!(
1523                iter.next().unwrap().to_rfc3339(),
1524                "2021-06-22T04:30:00+00:00",
1525                "schedule = {schedule}"
1526            );
1527            assert_eq!(
1528                iter.next().unwrap().to_rfc3339(),
1529                "2022-06-22T04:30:00+00:00",
1530                "schedule = {schedule}"
1531            );
1532            assert_eq!(
1533                iter.next().unwrap().to_rfc3339(),
1534                "2023-06-22T04:30:00+00:00",
1535                "schedule = {schedule}"
1536            );
1537            assert_eq!(
1538                iter.next().unwrap().to_rfc3339(),
1539                "2024-06-22T04:30:00+00:00",
1540                "schedule = {schedule}"
1541            );
1542            assert_eq!(
1543                iter.next().unwrap().to_rfc3339(),
1544                "2025-06-22T04:30:00+00:00",
1545                "schedule = {schedule}"
1546            );
1547        }
1548
1549        #[rstest]
1550        #[case(Utc, Utc)]
1551        #[case(Utc, Local)]
1552        #[case(Local, Utc)]
1553        #[case(Local, Local)]
1554        #[case(chrono_tz::EET, Utc)]
1555        #[case(Utc, chrono_tz::EET)]
1556        #[case(chrono_tz::EET, Local)]
1557        #[case(Local, chrono_tz::EET)]
1558        #[case(chrono_tz::EET, chrono_tz::EET)]
1559        #[case(chrono_tz::EET, chrono_tz::Canada::Eastern)]
1560        #[case(chrono_tz::Canada::Eastern, chrono_tz::EET)]
1561        #[timeout(Duration::from_secs(3))]
1562        fn test_rough_iterator<T1: TimeZone + Debug, T2: TimeZone + Debug>(
1563            #[case] current_tz: T1,
1564            #[case] schedule_tz: T2,
1565        ) {
1566            use std::collections::BTreeSet;
1567
1568            const TAKE_ITEMS: usize = (365 + 366) * 24 + 3;
1569
1570            let current = current_tz.with_ymd_and_hms(2024, 3, 1, 0, 0, 0).unwrap();
1571
1572            let tz_str = format!("{schedule_tz:?}").to_uppercase();
1573            let tz = if tz_str == "LOCAL" {
1574                None
1575            } else if tz_str == "Z" || tz_str == "UTC" {
1576                Some(String::from("UTC"))
1577            } else {
1578                Some(format!("{schedule_tz:?}"))
1579            };
1580
1581            let schedule = if let Some(tz) = tz {
1582                Schedule::new(format!("TZ={tz} @hourly")).unwrap()
1583            } else {
1584                Schedule::new(" @hourly").unwrap()
1585            };
1586
1587            let result = schedule.iter(&current).take(TAKE_ITEMS).collect::<Vec<_>>();
1588            assert_eq!(
1589                result.len(),
1590                TAKE_ITEMS,
1591                "current_tz={current_tz:?}, schedule_tz={schedule_tz:?}, schedule='{schedule}', last={}",
1592                result[result.len() - 1].to_rfc2822()
1593            );
1594
1595            // find duplicates in result
1596            let mut set = BTreeSet::new();
1597            set.extend(&result);
1598            assert_eq!(set.len(), result.len());
1599        }
1600    }
1601}