use crate::error::{PandRSError, Result};
use crate::temporal::core::{days_in_month, is_leap_year, Temporal};
use crate::temporal::frequency::Frequency;
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, Utc};
#[derive(Debug, Clone)]
pub struct DateRange<T: Temporal> {
start: T,
end: T,
freq: Frequency,
inclusive: bool,
}
impl<T: Temporal> DateRange<T> {
pub fn new(start: T, end: T, freq: Frequency, inclusive: bool) -> Result<Self> {
if start > end {
return Err(PandRSError::Consistency(
"Start date must be earlier than end date".to_string(),
));
}
Ok(DateRange {
start,
end,
freq,
inclusive,
})
}
pub fn generate(&self) -> Vec<T> {
let mut result = Vec::new();
let mut current = self.start.clone();
result.push(current.clone());
loop {
current = match self.freq {
Frequency::Secondly => current.add(Duration::seconds(1)),
Frequency::Minutely => current.add(Duration::minutes(1)),
Frequency::Hourly => current.add(Duration::hours(1)),
Frequency::Daily => current.add(Duration::days(1)),
Frequency::Weekly => current.add(Duration::weeks(1)),
Frequency::Monthly => {
let utc = current.to_utc();
let naive = utc.naive_utc();
let mut year = naive.year();
let mut month = naive.month() + 1;
if month > 12 {
month = 1;
year += 1;
}
let day = naive.day().min(days_in_month(year, month));
let new_naive = NaiveDateTime::new(
NaiveDate::from_ymd_opt(year, month, day)
.expect("operation should succeed"),
naive.time(),
);
let new_utc = DateTime::<Utc>::from_naive_utc_and_offset(new_naive, Utc);
T::from_str(&new_utc.to_rfc3339()).expect("operation should succeed")
}
Frequency::Quarterly => {
let utc = current.to_utc();
let naive = utc.naive_utc();
let mut year = naive.year();
let mut month = naive.month() + 3;
if month > 12 {
month = month - 12;
year += 1;
}
let day = naive.day().min(days_in_month(year, month));
let new_naive = NaiveDateTime::new(
NaiveDate::from_ymd_opt(year, month, day)
.expect("operation should succeed"),
naive.time(),
);
let new_utc = DateTime::<Utc>::from_naive_utc_and_offset(new_naive, Utc);
T::from_str(&new_utc.to_rfc3339()).expect("operation should succeed")
}
Frequency::Yearly => {
let utc = current.to_utc();
let naive = utc.naive_utc();
let year = naive.year() + 1i32;
let month = naive.month();
let day = if month == 2 && naive.day() == 29 && !is_leap_year(year as i32) {
28
} else {
naive.day()
};
let new_naive = NaiveDateTime::new(
NaiveDate::from_ymd_opt(year, month, day)
.expect("operation should succeed"),
naive.time(),
);
let new_utc = DateTime::<Utc>::from_naive_utc_and_offset(new_naive, Utc);
T::from_str(&new_utc.to_rfc3339()).expect("operation should succeed")
}
Frequency::Custom(duration) => current.add(duration),
};
if self.inclusive {
if current > self.end {
break;
}
} else if current >= self.end {
break;
}
result.push(current.clone());
}
result
}
}
pub fn date_range<T: Temporal>(
start: T,
end: T,
freq: Frequency,
inclusive: bool,
) -> Result<Vec<T>> {
DateRange::new(start, end, freq, inclusive).map(|range| range.generate())
}