use core::iter::FusedIterator;
use crate::{Error, NepaliDate};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct NepaliDateRange {
start: NepaliDate,
end: NepaliDate,
}
impl NepaliDateRange {
#[must_use]
pub const fn new(start: NepaliDate, end: NepaliDate) -> Option<Self> {
let (sy, sm, sd) = (start.year(), start.month_number(), start.day());
let (ey, em, ed) = (end.year(), end.month_number(), end.day());
let after = sy > ey || (sy == ey && (sm > em || (sm == em && sd > ed)));
if after {
None
} else {
Some(Self { start, end })
}
}
pub(crate) const fn new_unchecked(start: NepaliDate, end: NepaliDate) -> Self {
Self { start, end }
}
#[must_use = "the month may not exist"]
pub fn month(year: u16, month: u8) -> Result<Self, Error> {
let first = NepaliDate::new(year, month, 1)?;
Ok(Self::new_unchecked(first, first.last_day_of_month()))
}
#[must_use = "the year may be out of range"]
pub fn year(year: u16) -> Result<Self, Error> {
let first = NepaliDate::new(year, 1, 1)?;
let chaitra = NepaliDate::new(year, 12, 1)?;
Ok(Self::new_unchecked(first, chaitra.last_day_of_month()))
}
#[must_use]
pub const fn start(self) -> NepaliDate {
self.start
}
#[must_use]
pub const fn end(self) -> NepaliDate {
self.end
}
#[must_use]
#[allow(clippy::len_without_is_empty)] pub fn len(self) -> u32 {
self.end.to_serial() - self.start.to_serial() + 1
}
#[must_use]
pub fn contains(self, date: NepaliDate) -> bool {
self.start <= date && date <= self.end
}
#[must_use]
pub fn days(self) -> Days {
Days {
front: self.start.to_serial(),
back: self.end.to_serial(),
done: false,
}
}
}
impl IntoIterator for NepaliDateRange {
type Item = NepaliDate;
type IntoIter = Days;
fn into_iter(self) -> Days {
self.days()
}
}
#[derive(Clone, Debug)]
pub struct Days {
front: u32,
back: u32,
done: bool,
}
impl Iterator for Days {
type Item = NepaliDate;
fn next(&mut self) -> Option<NepaliDate> {
if self.done {
return None;
}
let date = NepaliDate::from_serial(self.front).ok()?;
if self.front == self.back {
self.done = true;
} else {
self.front += 1;
}
Some(date)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let n = if self.done {
0
} else {
usize::try_from(self.back - self.front + 1).unwrap_or(usize::MAX)
};
(n, Some(n))
}
}
impl DoubleEndedIterator for Days {
fn next_back(&mut self) -> Option<NepaliDate> {
if self.done {
return None;
}
let date = NepaliDate::from_serial(self.back).ok()?;
if self.front == self.back {
self.done = true;
} else {
self.back -= 1;
}
Some(date)
}
}
impl ExactSizeIterator for Days {}
impl FusedIterator for Days {}
#[cfg(test)]
mod tests {
use super::*;
use crate::calendar::{MAX_YEAR, MIN_YEAR};
fn bs(y: u16, m: u8, d: u8) -> NepaliDate {
NepaliDate::new(y, m, d).unwrap_or(NepaliDate::MIN)
}
#[test]
fn month_and_year_lengths() {
assert_eq!(
NepaliDateRange::month(2081, 4).map(NepaliDateRange::len),
Ok(32)
);
assert_eq!(
NepaliDateRange::year(2087).map(NepaliDateRange::len),
Ok(367)
);
assert_eq!(NepaliDateRange::year(2200), Err(Error::OutOfRange));
assert_eq!(NepaliDateRange::year(1900), Err(Error::OutOfRange));
assert_eq!(
NepaliDateRange::month(2081, 13),
Err(Error::InvalidMonth(13))
);
}
#[test]
fn every_year_length_is_the_sum_of_its_months() {
for y in MIN_YEAR..=MAX_YEAR {
let sum: u32 = (1..=12)
.map(|m| NepaliDateRange::month(y, m).map_or(0, NepaliDateRange::len))
.sum();
assert_eq!(
NepaliDateRange::year(y).map(NepaliDateRange::len),
Ok(sum),
"{y}"
);
}
}
#[test]
fn new_orders_like_ord() {
let dates = [
bs(2080, 12, 30),
bs(2081, 1, 1),
bs(2081, 1, 2),
bs(2081, 2, 1),
NepaliDate::MIN,
NepaliDate::MAX,
];
for a in dates {
for b in dates {
assert_eq!(NepaliDateRange::new(a, b).is_some(), a <= b, "{a:?} {b:?}");
}
}
let a = bs(2081, 1, 1);
assert_eq!(
NepaliDateRange::new(a, a).map(NepaliDateRange::len),
Some(1)
);
}
#[test]
fn const_construction() {
const R: Option<NepaliDateRange> = NepaliDateRange::new(NepaliDate::MIN, NepaliDate::MAX);
assert_eq!(R.map(NepaliDateRange::len), Some(109_212));
}
#[test]
fn days_iterator_both_ends_and_fused() {
let r = NepaliDateRange::new_unchecked(bs(2081, 4, 1), bs(2081, 4, 32));
assert_eq!(NepaliDateRange::month(2081, 4), Ok(r));
let mut days = r.days();
assert_eq!(days.len(), 32);
assert_eq!(days.size_hint(), (32, Some(32)));
assert_eq!(days.next_back(), Some(bs(2081, 4, 32)));
assert_eq!(days.next(), Some(bs(2081, 4, 1)));
assert_eq!(days.by_ref().count(), 30);
assert_eq!(days.len(), 0);
assert_eq!(days.next(), None);
assert_eq!(days.next_back(), None);
assert_eq!(days.next(), None);
let one = NepaliDateRange::new_unchecked(NepaliDate::MAX, NepaliDate::MAX);
let mut it = one.days();
assert_eq!(it.next_back(), Some(NepaliDate::MAX));
assert_eq!(it.next(), None);
assert_eq!(one.into_iter().count(), 1);
}
}