Adding support for date ranges for multi-day holidays

This commit is contained in:
Ian Roddis
2021-12-06 14:26:01 -04:00
parent 68ce7080c1
commit c4f87e1e9e
3 changed files with 122 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
use crate::date_range::*;
use chrono::naive::NaiveDate;
use chrono::{Datelike, Month, Weekday};
use serde::{Deserialize, Serialize};
@@ -65,11 +66,14 @@ pub enum DateSpec {
impl DateSpec {
/// Get exact date of event and its policy, if it occured in that year
fn resolve(&self, year: i32) -> Option<(NaiveDate, AdjustmentPolicy)> {
fn resolve(&self, year: i32) -> Option<(DateRange, AdjustmentPolicy)> {
use DateSpec::*;
match self {
SpecificDate { date, .. } => Some((*date, AdjustmentPolicy::NoAdjustment)),
SpecificDate { date, .. } => Some((
DateRange::new(*date, date.succ()),
AdjustmentPolicy::NoAdjustment,
)),
DayOfMonth {
month,
day,
@@ -83,10 +87,8 @@ impl DateSpec {
} else if until.is_some() && until.unwrap().year() < year {
None
} else {
Some((
NaiveDate::from_ymd(year, month.number_from_month(), *day),
*observed,
))
let date = NaiveDate::from_ymd(year, month.number_from_month(), *day);
Some((DateRange::new(date, date.succ()), *observed))
}
}
NthDayOccurance {
@@ -118,7 +120,7 @@ impl DateSpec {
date = date.checked_sub_signed(chrono::Duration::days(7)).unwrap();
off += 1;
}
Some((date, *observed))
Some((DateRange::new(date, date.succ()), *observed))
} else {
let mut date = NaiveDate::from_ymd(year, month_num, 1);
while date.weekday() != *dow {
@@ -129,7 +131,7 @@ impl DateSpec {
date = date.checked_add_signed(chrono::Duration::days(7)).unwrap();
off -= 1;
}
Some((date, *observed))
Some((DateRange::new(date, date.succ()), *observed))
}
}
}
@@ -157,15 +159,17 @@ pub struct Calendar {
}
impl Calendar {
fn adjust_workdays(&self, workdays: &Vec<(NaiveDate, AdjustmentPolicy)>) -> HashSet<NaiveDate> {
fn adjust_workdays(&self, workdays: &Vec<(DateRange, AdjustmentPolicy)>) -> HashSet<NaiveDate> {
let mut observed = HashSet::new();
for (date, policy) in workdays.iter() {
match self.adjust_workday(*date, policy, &observed) {
Some(workday) => {
observed.insert(workday);
for (date_range, policy) in workdays.iter() {
for date in date_range {
match self.adjust_workday(date, policy, &observed) {
Some(workday) => {
observed.insert(workday);
}
None => {}
}
None => {}
}
}
@@ -225,7 +229,7 @@ impl Calendar {
/// Get the set of all workdays in a given year
pub fn get_workdays(&self, date: NaiveDate) -> HashSet<NaiveDate> {
let workdays: Vec<(NaiveDate, AdjustmentPolicy)> = self
let workdays: Vec<(DateRange, AdjustmentPolicy)> = self
.exclude
.iter()
.map(|x| x.resolve(date.year()))

102
src/date_range.rs Normal file
View File

@@ -0,0 +1,102 @@
use chrono::naive::NaiveDate;
/*
pub type DateRange = Range<NaiveDate>;
fn iter_dates(range: DateRange) -> impl Iterator<Item = NaiveDate> {
let ndays = (range.end - range.start).num_days();
(0..ndays).map(move |x| range.start.checked_add_signed(Duration::days(x)).unwrap())
}
*/
pub struct DateRange {
start: NaiveDate,
end: NaiveDate,
}
impl DateRange {
pub fn new(start: NaiveDate, end: NaiveDate) -> Self {
DateRange { start, end }
}
pub fn contains(&self, date: NaiveDate) -> bool {
self.start <= date && date < self.end
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
pub struct DateRangeIterator {
cur: NaiveDate,
end: NaiveDate,
}
impl Iterator for DateRangeIterator {
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
if self.cur == self.end {
None
} else {
let ret = self.cur;
self.cur = self.cur.succ();
Some(ret)
}
}
}
impl IntoIterator for DateRange {
type Item = NaiveDate;
type IntoIter = DateRangeIterator;
fn into_iter(self) -> Self::IntoIter {
DateRangeIterator {
cur: self.start,
end: self.end,
}
}
}
impl IntoIterator for &DateRange {
type Item = NaiveDate;
type IntoIter = DateRangeIterator;
fn into_iter(self) -> Self::IntoIter {
DateRangeIterator {
cur: self.start,
end: self.end,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_range_bounds() {
let dr = DateRange {
start: NaiveDate::from_ymd(2021, 1, 1),
end: NaiveDate::from_ymd(2021, 2, 1),
};
assert!(!dr.is_empty());
assert!(dr.contains(NaiveDate::from_ymd(2021, 1, 1)));
assert!(dr.contains(NaiveDate::from_ymd(2021, 1, 15)));
assert!(!dr.contains(NaiveDate::from_ymd(2021, 2, 1)));
}
#[test]
fn test_range_iteration() {
let dr = DateRange {
start: NaiveDate::from_ymd(2021, 1, 1),
end: NaiveDate::from_ymd(2021, 2, 1),
};
let mut cnt = 0;
for _ in dr {
cnt += 1;
}
assert_eq!(cnt, 31);
}
}

View File

@@ -1,4 +1,5 @@
pub mod calendar;
pub mod date_range;
pub mod schedule;
fn main() {