use crate::shared::shared;
use crate::time::calendar::{Calendar, CalendarImpl};
use crate::time::date::{Date, Year};
use crate::time::weekday::Weekday;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JointCalendarRule {
JoinHolidays,
JoinBusinessDays,
}
pub struct JointCalendar;
impl JointCalendar {
pub fn new(calendars: Vec<Calendar>, rule: JointCalendarRule) -> Calendar {
assert!(!calendars.is_empty(), "at least one calendar required");
Calendar::from_impl(shared(Impl { rule, calendars }))
}
pub fn of_two(c1: Calendar, c2: Calendar, rule: JointCalendarRule) -> Calendar {
Self::new(vec![c1, c2], rule)
}
}
struct Impl {
rule: JointCalendarRule,
calendars: Vec<Calendar>,
}
impl CalendarImpl for Impl {
fn name(&self) -> String {
let prefix = match self.rule {
JointCalendarRule::JoinHolidays => "JoinHolidays(",
JointCalendarRule::JoinBusinessDays => "JoinBusinessDays(",
};
let mut out = String::from(prefix);
for (i, c) in self.calendars.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push_str(&c.name());
}
out.push(')');
out
}
fn is_weekend(&self, w: Weekday) -> bool {
match self.rule {
JointCalendarRule::JoinHolidays => self.calendars.iter().any(|c| c.is_weekend(w)),
JointCalendarRule::JoinBusinessDays => self.calendars.iter().all(|c| c.is_weekend(w)),
}
}
fn is_weekend_on(&self, date: Date) -> bool {
match self.rule {
JointCalendarRule::JoinHolidays => self.calendars.iter().any(|c| c.is_weekend_on(date)),
JointCalendarRule::JoinBusinessDays => {
self.calendars.iter().all(|c| c.is_weekend_on(date))
}
}
}
fn is_business_day(&self, date: Date) -> bool {
match self.rule {
JointCalendarRule::JoinHolidays => {
self.calendars.iter().all(|c| c.is_business_day(date))
}
JointCalendarRule::JoinBusinessDays => {
self.calendars.iter().any(|c| c.is_business_day(date))
}
}
}
fn holiday_horizon(&self) -> Option<Year> {
self.calendars
.iter()
.filter_map(|c| c.holiday_horizon())
.min()
}
fn holiday_first_year(&self) -> Option<Year> {
self.calendars
.iter()
.filter_map(|c| c.holiday_first_year())
.max()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::shared;
use crate::time::calendars::target::Target;
use crate::time::calendars::weekendsonly::WeekendsOnly;
use crate::time::date::Month;
struct SwitchingWeekendCal;
impl CalendarImpl for SwitchingWeekendCal {
fn name(&self) -> String {
"switching weekend".to_string()
}
fn is_weekend(&self, weekday: Weekday) -> bool {
weekday == Weekday::Friday || weekday == Weekday::Saturday
}
fn is_weekend_on(&self, date: Date) -> bool {
let weekday = date.weekday();
if date < Date::new(29, Month::June, 2013) {
weekday == Weekday::Thursday || weekday == Weekday::Friday
} else {
weekday == Weekday::Friday || weekday == Weekday::Saturday
}
}
fn is_business_day(&self, date: Date) -> bool {
!self.is_weekend_on(date)
}
}
struct HorizonCal(Year);
impl CalendarImpl for HorizonCal {
fn name(&self) -> String {
format!("horizon {}", self.0)
}
fn is_weekend(&self, weekday: Weekday) -> bool {
crate::time::calendar::is_weekend_sat_sun(weekday)
}
fn is_business_day(&self, date: Date) -> bool {
!self.is_weekend(date.weekday())
}
fn holiday_horizon(&self) -> Option<Year> {
Some(self.0)
}
}
fn horizon(year: Year) -> Calendar {
Calendar::from_impl(shared(HorizonCal(year)))
}
struct FirstYearCal(Year);
impl CalendarImpl for FirstYearCal {
fn name(&self) -> String {
format!("first year {}", self.0)
}
fn is_weekend(&self, weekday: Weekday) -> bool {
crate::time::calendar::is_weekend_sat_sun(weekday)
}
fn is_business_day(&self, date: Date) -> bool {
!self.is_weekend(date.weekday())
}
fn holiday_first_year(&self) -> Option<Year> {
Some(self.0)
}
}
fn first_year(year: Year) -> Calendar {
Calendar::from_impl(shared(FirstYearCal(year)))
}
const RULES: [JointCalendarRule; 2] = [
JointCalendarRule::JoinHolidays,
JointCalendarRule::JoinBusinessDays,
];
#[test]
fn horizon_comes_from_the_only_member_that_has_one() {
for rule in RULES {
let joint = JointCalendar::of_two(horizon(2050), Target::new(), rule);
assert_eq!(joint.holiday_horizon(), Some(2050));
}
}
#[test]
fn horizon_is_the_earliest_of_the_members() {
for rule in RULES {
let joint = JointCalendar::new(
vec![horizon(2050), WeekendsOnly::new(), horizon(2030)],
rule,
);
assert_eq!(joint.holiday_horizon(), Some(2030));
}
}
#[test]
fn horizon_is_none_without_tabulated_members() {
for rule in RULES {
let joint = JointCalendar::of_two(Target::new(), WeekendsOnly::new(), rule);
assert_eq!(joint.holiday_horizon(), None);
}
}
#[test]
fn horizon_recurses_into_nested_joint_calendars() {
for rule in RULES {
let inner = JointCalendar::of_two(horizon(2040), Target::new(), rule);
let joint = JointCalendar::of_two(WeekendsOnly::new(), inner, rule);
assert_eq!(joint.holiday_horizon(), Some(2040));
}
}
#[test]
fn first_year_comes_from_the_only_member_that_has_one() {
for rule in RULES {
let joint = JointCalendar::of_two(first_year(2012), Target::new(), rule);
assert_eq!(joint.holiday_first_year(), Some(2012));
}
}
#[test]
fn first_year_is_the_latest_of_the_members() {
for rule in RULES {
let joint = JointCalendar::new(
vec![first_year(2012), WeekendsOnly::new(), first_year(2000)],
rule,
);
assert_eq!(joint.holiday_first_year(), Some(2012));
}
}
#[test]
fn first_year_is_none_without_bounded_members() {
for rule in RULES {
let joint = JointCalendar::of_two(Target::new(), WeekendsOnly::new(), rule);
assert_eq!(joint.holiday_first_year(), None);
}
}
#[test]
fn first_year_recurses_into_nested_joint_calendars() {
for rule in RULES {
let inner = JointCalendar::of_two(first_year(2012), Target::new(), rule);
let joint = JointCalendar::of_two(WeekendsOnly::new(), inner, rule);
assert_eq!(joint.holiday_first_year(), Some(2012));
}
}
#[test]
fn join_holidays_is_holiday_if_any_is() {
let joint = JointCalendar::of_two(
Target::new(),
WeekendsOnly::new(),
JointCalendarRule::JoinHolidays,
);
let christmas = Date::new(25, Month::December, 2018); assert!(joint.is_holiday(christmas));
}
#[test]
fn join_business_days_is_business_if_any_is() {
let joint = JointCalendar::of_two(
Target::new(),
WeekendsOnly::new(),
JointCalendarRule::JoinBusinessDays,
);
let christmas = Date::new(25, Month::December, 2018);
assert!(joint.is_business_day(christmas));
}
#[test]
fn name_lists_members() {
let joint = JointCalendar::of_two(
Target::new(),
WeekendsOnly::new(),
JointCalendarRule::JoinHolidays,
);
assert_eq!(joint.name(), "JoinHolidays(TARGET, weekends only)");
}
#[test]
fn date_aware_weekends_are_joined() {
let switching = Calendar::from_impl(shared(SwitchingWeekendCal));
let joint = JointCalendar::of_two(
switching,
WeekendsOnly::new(),
JointCalendarRule::JoinHolidays,
);
let thursday_before_switch = Date::new(27, Month::June, 2013);
assert_eq!(thursday_before_switch.weekday(), Weekday::Thursday);
assert!(joint.is_holiday(thursday_before_switch));
assert!(joint.is_weekend_on(thursday_before_switch));
assert!(!joint.is_weekend(thursday_before_switch.weekday()));
assert!(
joint
.holiday_list(thursday_before_switch, thursday_before_switch, false)
.is_empty()
);
}
}