use crate::calendar::{Calendar, is_business_day, is_holiday};
use crate::date::Date;
#[derive(Debug, Clone, Copy)]
pub struct Composite {
pub kinds: &'static [Calendar],
}
impl Composite {
#[must_use]
pub const fn new(kinds: &'static [Calendar]) -> Self {
Self { kinds }
}
#[must_use]
pub fn is_holiday(&self, date: Date) -> bool {
self.kinds.iter().any(|&cal| is_holiday(date, cal))
}
#[must_use]
pub fn is_business_day(&self, date: Date) -> bool {
self.kinds.iter().all(|&cal| is_business_day(date, cal))
}
}
#[derive(Debug, Clone, Copy)]
pub struct JointBusiness {
pub kinds: &'static [Calendar],
}
impl JointBusiness {
#[must_use]
pub const fn new(kinds: &'static [Calendar]) -> Self {
Self { kinds }
}
#[must_use]
pub fn is_business_day(&self, date: Date) -> bool {
self.kinds.iter().all(|&cal| is_business_day(date, cal))
}
#[must_use]
pub fn is_holiday(&self, date: Date) -> bool {
self.kinds.iter().any(|&cal| is_holiday(date, cal))
}
}
#[cfg(test)]
mod tests {
use super::*;
static EUR_USD: &[Calendar] = &[Calendar::Target2, Calendar::UnitedStates];
static EUR_CHF: &[Calendar] = &[Calendar::Target2, Calendar::Switzerland];
static US_UK: &[Calendar] = &[Calendar::UnitedStates, Calendar::UnitedKingdom];
#[test]
fn composite_is_holiday_on_any_leg() {
let c = Composite::new(EUR_USD);
assert!(c.is_holiday(Date::ymd(2024, 7, 4).unwrap()));
assert!(c.is_holiday(Date::ymd(2024, 5, 1).unwrap()));
assert!(!c.is_holiday(Date::ymd(2024, 3, 15).unwrap()));
}
#[test]
fn composite_business_day_requires_all_open() {
let c = Composite::new(EUR_USD);
assert!(!c.is_business_day(Date::ymd(2024, 7, 4).unwrap()));
assert!(!c.is_business_day(Date::ymd(2024, 5, 1).unwrap()));
assert!(c.is_business_day(Date::ymd(2024, 3, 15).unwrap()));
assert!(!c.is_business_day(Date::ymd(2024, 3, 16).unwrap()));
}
#[test]
fn joint_business_business_day_requires_all_open() {
let j = JointBusiness::new(EUR_CHF);
assert!(!j.is_business_day(Date::ymd(2024, 5, 1).unwrap()));
assert!(!j.is_business_day(Date::ymd(2024, 8, 1).unwrap()));
assert!(j.is_business_day(Date::ymd(2024, 3, 15).unwrap()));
}
#[test]
fn joint_business_is_holiday_on_any_leg() {
let j = JointBusiness::new(US_UK);
assert!(j.is_holiday(Date::ymd(2024, 7, 4).unwrap()));
assert!(j.is_holiday(Date::ymd(2024, 8, 26).unwrap()));
assert!(!j.is_holiday(Date::ymd(2024, 3, 15).unwrap()));
}
#[test]
fn single_calendar_composite_matches_underlying() {
static SINGLE: &[Calendar] = &[Calendar::Target2];
let c = Composite::new(SINGLE);
let d = Date::ymd(2024, 12, 25).unwrap();
assert_eq!(
c.is_holiday(d),
crate::calendar::is_holiday(d, Calendar::Target2)
);
let d2 = Date::ymd(2024, 3, 15).unwrap();
assert_eq!(
c.is_holiday(d2),
crate::calendar::is_holiday(d2, Calendar::Target2)
);
}
#[test]
fn empty_composite_has_no_holidays_and_only_weekend_filters_business_days() {
static EMPTY: &[Calendar] = &[];
let c = Composite::new(EMPTY);
assert!(!c.is_holiday(Date::ymd(2024, 12, 25).unwrap()));
assert!(c.is_business_day(Date::ymd(2024, 3, 16).unwrap())); }
}