`NaiveDateDaysIterator` (`NaiveDate::iter_days`) behaves unexpectedly when reversed
The iterator returned by NaiveDate::iter_days behaves unexpectedly when iterated in reverse. Consider:
#[test]
fn test() {
let start = chrono::NaiveDate::from_ymd_opt(2025, 10, 10).unwrap();
let mut next_seven_rev1: Vec<_> = start.iter_days().take(7).collect();
next_seven_rev1.reverse();
let next_seven_rev2: Vec<_> = start.iter_days().take(7).rev().collect();
assert_eq!(next_seven_rev1, next_seven_rev2);
}
I would expect the assertion here to pass, but it does not:
assertion `left == right` failed
left: [2025-10-16, 2025-10-15, 2025-10-14, 2025-10-13, 2025-10-12, 2025-10-11, 2025-10-10]
right: [-258092-07-28]
My brief analysis of this follows. Take it with a grain of salt though, as I might be misunderstanding exactly how the various iterator traits are expected to interact.
From what I can tell, I think this behaviour is due to NaiveDateDaysIterator implementing DoubleEndedIterator and ExactSizeIterator, which the Take iterator takes advantage of, but:
-
start.iter_days().len()reports the number of days untilNaiveDate::MAX -
start.iter_days().next_back()is notNaiveDate::MAX
And in particular, the latter seems to be the source of the issue. Per the docs on DoubleEndedIterator:
It is important to note that both back and forth work on the same range, and do not cross: iteration is over when they meet in the middle.
So start.iter_days().next_back() should be equivalent to start.iter_days().last(), but this is not the case.
If I'm right, then the fix would seem to be to remember the starting date in NaiveDateDaysIterator and wrap around there instead of at NaiveDate::MIN, but that would break any current uses of NaiveDate::iter_days().rev(). So it might also necessitate adding some kind of NaiveDate::iter_days_backwards() API. In any case, this would be a backwards-incompatible change.
I'd be open to taking a PR for this on 0.4.x. It seems like a clear correctness fix and I don't think many people are relying on the old behavior.