use chrono::{Datelike, NaiveDate};
use findates::calendar;
use findates::conventions::{AdjustRule, Frequency};
use findates::schedule::{schedule_next_adjusted, Schedule};
use std::collections::HashSet;
struct ScheduleSetup {
cal: calendar::Calendar,
}
impl ScheduleSetup {
fn new() -> Self {
let mut basic_cal = calendar::basic_calendar();
let christmas_day = NaiveDate::from_ymd_opt(2023, 12, 25).unwrap();
let boxing_day = NaiveDate::from_ymd_opt(2023, 12, 26).unwrap();
let new_holidays: HashSet<NaiveDate> = [christmas_day, boxing_day].into_iter().collect();
basic_cal.add_holidays(&new_holidays);
Self { cal: basic_cal }
}
}
#[test]
fn daily_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 29).unwrap();
let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModFollowing),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 2).unwrap());
let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 9, 30).unwrap()); let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Preceding),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 2).unwrap());
let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Nearest),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 2).unwrap());
}
#[test]
fn weekly_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 29).unwrap();
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.weekday(), res.weekday());
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Preceding),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.weekday(), res.weekday());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 12, 18).unwrap();
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Preceding),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 12, 22).unwrap()); let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 12, 18).unwrap();
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Following),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 12, 27).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 12, 18).unwrap();
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: None,
adjust_rule: Some(AdjustRule::Following),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 12, 25).unwrap());
assert_eq!(anchor.weekday(), res.weekday());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 12, 26).unwrap(); let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModFollowing),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2024, 1, 2).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 12, 23).unwrap(); let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModFollowing),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 12, 29).unwrap());
}
#[test]
fn biweekly_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let sch = Schedule {
frequency: Frequency::Biweekly,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.weekday(), res.weekday());
let sch = Schedule {
frequency: Frequency::Biweekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Nearest),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_ne!(anchor.weekday(), res.weekday());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 13).unwrap());
}
#[test]
fn fourweeks_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let sch = Schedule {
frequency: Frequency::EveryFourthWeek,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.weekday(), res.weekday());
let sch = Schedule {
frequency: Frequency::EveryFourthWeek,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::Nearest),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_ne!(anchor.weekday(), res.weekday());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 27).unwrap());
}
#[test]
fn monthly_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let sch = Schedule {
frequency: Frequency::Monthly,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 10, 30).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 10, 31).unwrap();
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_ne!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 11, 30).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 2, 18).unwrap();
let sch = Schedule {
frequency: Frequency::Monthly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModPreceding),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_ne!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 3, 17).unwrap());
}
#[test]
fn bimonthly_next_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let sch = Schedule {
frequency: Frequency::Bimonthly,
calendar: Some(&cal),
adjust_rule: None,
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 11, 30).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 10, 31).unwrap();
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 12, 31).unwrap());
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 2, 18).unwrap();
let sch = Schedule {
frequency: Frequency::Bimonthly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModPreceding),
};
let res = schedule_next_adjusted(&sch, anchor).unwrap();
assert_eq!(anchor.day(), res.day());
assert_eq!(res, NaiveDate::from_ymd_opt(2023, 4, 18).unwrap());
}
#[test]
fn daily_generator_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let end: NaiveDate = NaiveDate::from_ymd_opt(2023, 10, 9).unwrap();
let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: None,
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-09-30",
"2023-10-01",
"2023-10-02",
"2023-10-03",
"2023-10-04",
"2023-10-05",
"2023-10-06",
"2023-10-07",
"2023-10-08",
"2023-10-09",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
let sch = Schedule {
frequency: Frequency::Daily,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModPreceding),
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-09-29",
"2023-10-02",
"2023-10-03",
"2023-10-04",
"2023-10-05",
"2023-10-06",
"2023-10-09",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
}
#[test]
fn weekly_generator_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 9, 30).unwrap();
let end: NaiveDate = NaiveDate::from_ymd_opt(2023, 11, 5).unwrap();
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: None,
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-09-30",
"2023-10-07",
"2023-10-14",
"2023-10-21",
"2023-10-28",
"2023-11-04",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
let sch = Schedule {
frequency: Frequency::Weekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModFollowing),
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-09-29",
"2023-10-09",
"2023-10-16",
"2023-10-23",
"2023-10-30",
"2023-11-06",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
}
#[test]
fn biweekly_generator_test() {
let setup = ScheduleSetup::new();
let cal = setup.cal;
let anchor: NaiveDate = NaiveDate::from_ymd_opt(2023, 11, 26).unwrap();
let end: NaiveDate = NaiveDate::from_ymd_opt(2024, 2, 5).unwrap();
let sch = Schedule {
frequency: Frequency::Biweekly,
calendar: Some(&cal),
adjust_rule: None,
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-11-26",
"2023-12-10",
"2023-12-24",
"2024-01-07",
"2024-01-21",
"2024-02-04",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
let sch = Schedule {
frequency: Frequency::Biweekly,
calendar: Some(&cal),
adjust_rule: Some(AdjustRule::ModPreceding),
};
let dates = sch.generate(&anchor, &end).unwrap();
let dates_str = [
"2023-11-24",
"2023-12-08",
"2023-12-22",
"2024-01-05",
"2024-01-19",
"2024-02-02",
];
let expected_dates: Vec<NaiveDate> = dates_str
.into_iter()
.map(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").unwrap())
.collect();
assert_eq!(expected_dates, dates);
}
#[test]
fn frequency_zero_generate_returns_end_date_test() {
let anchor = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
let end = NaiveDate::from_ymd_opt(2025, 1, 15).unwrap();
let sched = Schedule {
frequency: Frequency::Zero,
calendar: None,
adjust_rule: None,
};
let dates = sched.generate(&anchor, &end).unwrap();
assert_eq!(dates.len(), 1);
assert_eq!(dates[0], end);
}
#[test]
fn frequency_zero_generate_with_adjustment_test() {
let setup = ScheduleSetup::new();
let anchor = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
let end = NaiveDate::from_ymd_opt(2025, 3, 15).unwrap(); let sched = Schedule {
frequency: Frequency::Zero,
calendar: Some(&setup.cal),
adjust_rule: Some(AdjustRule::Following),
};
let dates = sched.generate(&anchor, &end).unwrap();
assert_eq!(dates.len(), 1);
assert_eq!(dates[0], NaiveDate::from_ymd_opt(2025, 3, 17).unwrap());
}
#[test]
fn generate_end_before_anchor_returns_err_test() {
let anchor = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
let before = NaiveDate::from_ymd_opt(2024, 6, 14).unwrap();
let sched = Schedule {
frequency: Frequency::Monthly,
calendar: None,
adjust_rule: None,
};
assert!(sched.generate(&anchor, &before).is_err());
}
#[test]
fn generate_end_equal_anchor_returns_err_test() {
let anchor = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
let sched = Schedule {
frequency: Frequency::Monthly,
calendar: None,
adjust_rule: None,
};
assert!(sched.generate(&anchor, &anchor).is_err());
}
#[test]
fn schedule_next_adjusted_zero_returns_none_test() {
let cal = calendar::basic_calendar();
let anchor = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
let sched = Schedule::new(Frequency::Zero, Some(&cal), Some(AdjustRule::Following));
assert_eq!(schedule_next_adjusted(&sched, anchor), None);
let sched = Schedule::new(Frequency::Zero, None, None);
assert_eq!(schedule_next_adjusted(&sched, anchor), None);
}
#[test]
fn generate_nominal_date_integrity_test() {
let cal = calendar::basic_calendar();
let anchor = NaiveDate::from_ymd_opt(2025, 7, 4).unwrap(); let end = NaiveDate::from_ymd_opt(2027, 7, 4).unwrap();
let sched = Schedule::new(Frequency::Annual, Some(&cal), Some(AdjustRule::Following));
let dates = sched.generate(&anchor, &end).unwrap();
assert_eq!(dates[1], NaiveDate::from_ymd_opt(2026, 7, 6).unwrap());
assert_eq!(dates[2], NaiveDate::from_ymd_opt(2027, 7, 5).unwrap());
}
#[test]
fn end_of_month_weekend_adjustment_test() {
let cal = calendar::basic_calendar();
let anchor = NaiveDate::from_ymd_opt(2024, 5, 31).unwrap(); let end = NaiveDate::from_ymd_opt(2024, 9, 2).unwrap();
let sched = Schedule::new(Frequency::EndOfMonth, Some(&cal), Some(AdjustRule::Following));
let dates = sched.generate(&anchor, &end).unwrap();
assert_eq!(dates[0], NaiveDate::from_ymd_opt(2024, 5, 31).unwrap());
assert_eq!(dates[1], NaiveDate::from_ymd_opt(2024, 7, 1).unwrap());
assert_eq!(dates[2], NaiveDate::from_ymd_opt(2024, 7, 31).unwrap());
assert_eq!(dates[3], NaiveDate::from_ymd_opt(2024, 9, 2).unwrap());
}