chrono_kit/iter/
naive_datetime_range_iter.rs

1use super::naive_datetime_iter::NaiveDatetimeIterError;
2use super::naive_datetime_iter::NaiveDatetimeIterator;
3use chrono::{Duration, NaiveDateTime};
4
5/// An iterator that produces consecutive datetime ranges
6///
7/// This iterator yields tuples of `(start, end)` datetimes where each range
8/// represents a time period between consecutive steps.
9///
10/// # Examples
11///
12/// Forward iteration:
13/// ```
14/// use chrono_kit::iter::NaiveDatetimeRangeIterator;
15/// use chrono::{NaiveDateTime, Duration};
16///
17/// let start = NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
18/// let end = NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
19/// let step = Duration::days(1);
20///
21/// let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
22/// assert_eq!(iter.next(), Some((start, start + step)));
23/// assert_eq!(iter.next(), Some((start + step, end)));
24/// ```
25///
26/// Reverse iteration:
27/// ```
28/// use chrono_kit::iter::NaiveDatetimeRangeIterator;
29/// use chrono::{NaiveDateTime, Duration};
30///
31/// let start = NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
32/// let end = NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
33/// let step = Duration::days(-1);
34///
35/// let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
36/// assert_eq!(iter.next(), Some((end + step, end)));
37/// assert_eq!(iter.next(), Some((start, end + step)));
38/// ```
39pub struct NaiveDatetimeRangeIterator {
40    datetime_iter: NaiveDatetimeIterator,
41    current: Option<NaiveDateTime>,
42    asc: bool,
43}
44
45impl NaiveDatetimeRangeIterator {
46    /// Creates a new DatetimeRangeIterator
47    ///
48    /// # Arguments
49    /// * `start` - The starting datetime (inclusive)
50    /// * `end` - The ending datetime (inclusive)
51    /// * `step` - The duration between each step (must be non-zero)
52    ///
53    /// # Errors
54    /// Returns `DatetimeIterError` if:
55    /// - `step` is zero
56    /// - `start` and `end` don't form a valid range for the given step
57    pub fn new(
58        start: NaiveDateTime,
59        end: NaiveDateTime,
60        step: Duration,
61    ) -> Result<Self, NaiveDatetimeIterError> {
62        let datetime_iter = NaiveDatetimeIterator::new(start, end, step)?;
63
64        Ok(NaiveDatetimeRangeIterator {
65            datetime_iter,
66            current: None,
67            asc: step > Duration::zero(),
68        })
69    }
70}
71
72impl Iterator for NaiveDatetimeRangeIterator {
73    type Item = (NaiveDateTime, NaiveDateTime);
74
75    fn next(&mut self) -> Option<Self::Item> {
76        let start = match self.current {
77            Some(dt) => dt,
78            None => {
79                let first = self.datetime_iter.next()?;
80                self.current = Some(first);
81                first
82            }
83        };
84
85        let end = self.datetime_iter.next()?;
86        self.current = Some(end);
87        if self.asc {
88            Some((start, end))
89        } else {
90            Some((end, start))
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use chrono::NaiveDateTime;
99
100    #[test]
101    fn test_ascending_range_iteration() {
102        let start =
103            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
104        let end =
105            NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
106        let step = Duration::days(1);
107
108        let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
109        assert_eq!(iter.next(), Some((start, start + step)));
110        assert_eq!(iter.next(), Some((start + step, end)));
111        assert_eq!(iter.next(), None);
112    }
113
114    #[test]
115    fn test_descending_range_iteration() {
116        let start =
117            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
118        let end =
119            NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
120        let step = Duration::days(-1);
121
122        let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
123        assert_eq!(iter.next(), Some((end + step, end)));
124        assert_eq!(iter.next(), Some((start, end + step)));
125        assert_eq!(iter.next(), None);
126    }
127
128    #[test]
129    fn test_non_integer_period() {
130        let start =
131            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
132        let end =
133            NaiveDateTime::parse_from_str("2023-01-01 12:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
134        let step = Duration::hours(9);
135
136        let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
137        assert_eq!(iter.next(), Some((start, start + step)));
138        assert_eq!(iter.next(), Some((start + step, end)));
139        assert_eq!(iter.next(), None);
140    }
141
142    #[test]
143    fn test_single_range() {
144        let start =
145            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
146        let end =
147            NaiveDateTime::parse_from_str("2023-01-01 12:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
148        let step = Duration::hours(12);
149
150        let mut iter = NaiveDatetimeRangeIterator::new(start, end, step).unwrap();
151        assert_eq!(iter.next(), Some((start, end)));
152        assert_eq!(iter.next(), None);
153    }
154
155    #[test]
156    fn test_zero_step_error() {
157        let start =
158            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
159        let end =
160            NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
161        let step = Duration::zero();
162
163        let result = NaiveDatetimeRangeIterator::new(start, end, step);
164        assert!(matches!(result, Err(NaiveDatetimeIterError::ZeroStep)));
165    }
166
167    #[test]
168    fn test_invalid_range_error() {
169        let start =
170            NaiveDateTime::parse_from_str("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
171        let end =
172            NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
173        let step = Duration::days(1);
174
175        let result = NaiveDatetimeRangeIterator::new(start, end, step);
176        assert!(matches!(
177            result,
178            Err(NaiveDatetimeIterError::InvalidRange { .. })
179        ));
180    }
181}