Skip to main content

time/iter/
date_iter.rs

1//! An iterator over a range of [`Date`]s, yielding each day from start to end inclusive.
2
3use core::iter::FusedIterator;
4
5use crate::Date;
6
7/// An iterator over a range of [`Date`]s, yielding each day from start to end inclusive.
8///
9/// This struct is created by [`Date::iter_to`] or [`DateIter::new`].
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct DateIter {
12    front: Date,
13    back: Option<Date>,
14}
15
16impl DateIter {
17    /// Create a new `DateIter` from `start` to `end` inclusive.
18    ///
19    /// If `start > end`, the iterator will be empty.
20    ///
21    /// ```rust
22    /// # use time::iter::DateIter;
23    /// # use time_macros::date;
24    /// let mut iter = DateIter::new(date!(2019-01-01), date!(2019-01-03));
25    /// assert_eq!(iter.next(), Some(date!(2019-01-01)));
26    /// assert_eq!(iter.next(), Some(date!(2019-01-02)));
27    /// assert_eq!(iter.next(), Some(date!(2019-01-03)));
28    /// assert_eq!(iter.next(), None);
29    /// ```
30    #[inline]
31    pub const fn new(start: Date, end: Date) -> Self {
32        Self {
33            front: start,
34            back: if start.as_i32() <= end.as_i32() {
35                Some(end)
36            } else {
37                None
38            },
39        }
40    }
41
42    /// Returns `true` if the iterator is empty, `false` otherwise.
43    #[inline]
44    const fn is_exhausted(&self) -> bool {
45        self.back.is_none()
46    }
47
48    /// Make the iterator exhausted.
49    #[inline]
50    const fn make_exhausted(&mut self) {
51        self.back = None;
52    }
53
54    /// Return the number of days from `front` to `back`. Uses ordinal arithmetic as a fast path
55    /// when both dates fall in the same year, avoiding the more expensive Julian day computation.
56    #[inline]
57    const fn days_between(front: Date, back: Date) -> i32 {
58        if front.year() == back.year() {
59            back.ordinal() as i32 - front.ordinal() as i32
60        } else {
61            back.to_julian_day() - front.to_julian_day()
62        }
63    }
64}
65
66impl Iterator for DateIter {
67    type Item = Date;
68
69    #[inline]
70    fn next(&mut self) -> Option<Self::Item> {
71        let current = self.front;
72        let back = self.back?;
73
74        if current < back {
75            // Safety: `current < back`, so `current` has a successor.
76            self.front = unsafe { current.next_day().unwrap_unchecked() };
77        } else {
78            self.make_exhausted();
79        }
80
81        Some(current)
82    }
83
84    #[inline]
85    fn size_hint(&self) -> (usize, Option<usize>) {
86        let len = self.len();
87        (len, Some(len))
88    }
89
90    #[inline]
91    fn count(self) -> usize {
92        self.len()
93    }
94
95    #[inline]
96    fn last(self) -> Option<Self::Item> {
97        self.back
98    }
99
100    #[inline]
101    fn nth(&mut self, n: usize) -> Option<Self::Item> {
102        let back = self.back?;
103        let front = self.front;
104
105        let same_year = front.year() == back.year();
106        let remaining = if same_year {
107            (back.ordinal() - front.ordinal()) as usize
108        } else {
109            (back.to_julian_day() - front.to_julian_day()) as usize
110        };
111
112        if n > remaining {
113            self.make_exhausted();
114            return None;
115        }
116
117        // Fast path for when the result is in the same year, avoiding the more expensive Julian day
118        // computation.
119        let result = if same_year
120            || front.ordinal() as usize <= if front.is_in_leap_year() { 366 } else { 365 } - n
121        {
122            // Safety: We know that we're staying in the same year and that the resulting ordinal is
123            // valid.
124            unsafe { front.add_days_unchecked(n as i32) }
125        } else {
126            // Safety: `n <= remaining = back_jd - front_jd`, so `front_jd + n <= back_jd`.
127            unsafe { Date::from_julian_day_unchecked(front.to_julian_day() + n as i32) }
128        };
129
130        if n == remaining {
131            self.make_exhausted();
132        } else {
133            // Safety: `result < back`, so `result` has a successor.
134            self.front = unsafe { result.next_day().unwrap_unchecked() };
135        }
136
137        Some(result)
138    }
139
140    #[inline]
141    fn max(self) -> Option<Self::Item> {
142        self.back
143    }
144
145    #[inline]
146    fn min(self) -> Option<Self::Item> {
147        (!self.is_exhausted()).then_some(self.front)
148    }
149
150    #[inline]
151    fn is_sorted(self) -> bool {
152        true
153    }
154}
155
156impl DoubleEndedIterator for DateIter {
157    #[inline]
158    fn next_back(&mut self) -> Option<Self::Item> {
159        let back = self.back?;
160
161        if self.front < back {
162            // Safety: `front < back`, so `back` has a predecessor.
163            self.back = Some(unsafe { back.previous_day().unwrap_unchecked() });
164        } else {
165            self.make_exhausted();
166        }
167
168        Some(back)
169    }
170
171    #[inline]
172    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
173        let back = self.back?;
174        let front = self.front;
175
176        let same_year = front.year() == back.year();
177        let remaining = if same_year {
178            (back.ordinal() - front.ordinal()) as usize
179        } else {
180            (back.to_julian_day() - front.to_julian_day()) as usize
181        };
182
183        if n > remaining {
184            self.make_exhausted();
185            return None;
186        }
187
188        let result = if same_year || back.ordinal() as usize > n {
189            // Safety: `back.ordinal() > n`, so subtracting stays within `back`'s year.
190            unsafe { back.add_days_unchecked(-(n as i32)) }
191        } else {
192            // Safety: `n <= remaining = back_jd - front_jd`, so `back_jd - n >= front_jd`.
193            unsafe { Date::from_julian_day_unchecked(back.to_julian_day() - n as i32) }
194        };
195
196        if n == remaining {
197            self.make_exhausted();
198        } else {
199            // Safety: `result > front`, so `result` has a predecessor.
200            self.back = Some(unsafe { result.previous_day().unwrap_unchecked() });
201        }
202
203        Some(result)
204    }
205}
206
207impl ExactSizeIterator for DateIter {
208    #[inline]
209    fn len(&self) -> usize {
210        let Some(back) = self.back else {
211            return 0;
212        };
213
214        Self::days_between(self.front, back) as usize + 1
215    }
216}
217
218impl FusedIterator for DateIter {}