croner/iterator.rs
1use crate::{Cron, CronError, Direction};
2use chrono::{DateTime, TimeZone, Duration};
3
4#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
5pub struct CronIterator<Tz>
6where
7 Tz: TimeZone,
8{
9 cron: Cron,
10 current_time: DateTime<Tz>,
11 is_first: bool,
12 inclusive: bool,
13 direction: Direction,
14 pending_ambiguous_dt: Option<DateTime<Tz>>,
15}
16
17impl<Tz> CronIterator<Tz>
18where
19 Tz: TimeZone,
20{
21 /// Creates a new `CronIterator`.
22 ///
23 /// # Arguments
24 ///
25 /// * `cron` - The `Cron` schedule instance.
26 /// * `start_time` - The `DateTime` to start iterating from.
27 /// * `inclusive` - Whether the `start_time` should be included in the results if it matches.
28 /// * `direction` - The direction to iterate in (Forward or Backward).
29 pub fn new(
30 cron: Cron,
31 start_time: DateTime<Tz>,
32 inclusive: bool,
33 direction: Direction,
34 ) -> Self {
35 CronIterator {
36 cron,
37 current_time: start_time,
38 is_first: true,
39 inclusive,
40 direction,
41 pending_ambiguous_dt: None,
42 }
43 }
44}
45
46impl<Tz> Iterator for CronIterator<Tz>
47where
48 Tz: TimeZone + Clone + Copy,
49{
50 type Item = DateTime<Tz>;
51
52 fn next(&mut self) -> Option<Self::Item> {
53 // Step 1: Check for and yield a pending ambiguous datetime first.
54 // This handles the second occurrence of a time during DST fallback.
55 if let Some(pending_dt_to_yield) = self.pending_ambiguous_dt.take() {
56 // After yielding the second ambiguous time, advance current_time past it.
57 // Clone pending_dt_to_yield because it's about to be returned,
58 // but we need its value to calculate the next `self.current_time`.
59 self.current_time = pending_dt_to_yield.clone().checked_add_signed(match self.direction { // Fixed E0382: pending_dt_to_yield
60 Direction::Forward => Duration::seconds(1),
61 Direction::Backward => Duration::seconds(-1),
62 }).ok_or(CronError::InvalidTime).ok()?;
63 return Some(pending_dt_to_yield);
64 }
65
66 // Determine if the search should be inclusive based on whether it's the first run.
67 let inclusive_search = if self.is_first {
68 self.is_first = false;
69 self.inclusive
70 } else {
71 false // Subsequent searches are always exclusive of the last actual point in time.
72 };
73
74 let result = self.cron.find_occurrence(&self.current_time, inclusive_search, self.direction);
75
76 match result {
77 Ok((found_time, optional_second_ambiguous_dt)) => {
78 // This `found_time` is the one we will return in this iteration.
79
80 // If there's a second ambiguous datetime (for interval jobs),
81 // store it to be yielded on the *next* call to next().
82 // And importantly, set `self.current_time` to advance *past* this second ambiguous time
83 // so the *next* search for a *new* naive time is correct.
84 if let Some(second_ambiguous_dt) = optional_second_ambiguous_dt {
85 // Clone second_ambiguous_dt because it's stored in self.pending_ambiguous_dt
86 // AND used to calculate the next self.current_time.
87 self.pending_ambiguous_dt = Some(second_ambiguous_dt.clone()); // Fixed E0382: second_ambiguous_dt
88
89 // Advance `self.current_time` past the latest of the ambiguous pair.
90 // This ensures the next `find_occurrence` call searches for the next unique naive time.
91 self.current_time = second_ambiguous_dt.checked_add_signed(match self.direction {
92 Direction::Forward => Duration::seconds(1),
93 Direction::Backward => Duration::seconds(-1),
94 }).ok_or(CronError::InvalidTime).ok()?;
95
96 } else {
97 // Case: No second ambiguous time (either not an overlap, or fixed-time job).
98 // Advance `self.current_time` simply past the `found_time`.
99 // Clone found_time because it's used to calculate the next self.current_time
100 // AND returned at the end of this block.
101 self.current_time = found_time.clone().checked_add_signed(match self.direction { // Fixed E0382: found_time
102 Direction::Forward => Duration::seconds(1),
103 Direction::Backward => Duration::seconds(-1),
104 }).ok_or(CronError::InvalidTime).ok()?;
105 }
106
107 // Finally, return the found_time for the current iteration.
108 // This `found_time` is the original value received from `find_occurrence`.
109 Some(found_time)
110 }
111 Err(CronError::TimeSearchLimitExceeded) => None,
112 Err(e) => {
113 eprintln!("CronIterator encountered an error: {e:?}");
114 None
115 }
116 }
117 }
118}