rateslib/scheduling/calendars/
union_cal.rs1use chrono::prelude::*;
2use pyo3::pyclass;
3use serde::{Deserialize, Serialize};
4
5use crate::scheduling::{ndt, Cal, CalendarAdjustment, DateRoll};
6
7#[pyclass(module = "rateslib.rs")]
16#[derive(Clone, Default, Debug, Serialize, Deserialize)]
17pub struct UnionCal {
18 pub calendars: Vec<Cal>,
20 pub settlement_calendars: Option<Vec<Cal>>,
22}
23
24impl UnionCal {
25 pub fn new(calendars: Vec<Cal>, settlement_calendars: Option<Vec<Cal>>) -> Self {
37 UnionCal {
38 calendars,
39 settlement_calendars,
40 }
41 }
42}
43
44impl DateRoll for UnionCal {
45 fn is_weekday(&self, date: &NaiveDateTime) -> bool {
46 self.calendars.iter().all(|cal| cal.is_weekday(date))
47 }
48
49 fn is_holiday(&self, date: &NaiveDateTime) -> bool {
50 self.calendars.iter().any(|cal| cal.is_holiday(date))
51 }
52
53 fn is_settlement(&self, date: &NaiveDateTime) -> bool {
54 self.settlement_calendars
55 .as_ref()
56 .map_or(true, |v| !v.iter().any(|cal| cal.is_non_bus_day(date)))
57 }
58}
59
60impl CalendarAdjustment for UnionCal {}
61
62impl<T> PartialEq<T> for UnionCal
63where
64 T: DateRoll,
65{
66 fn eq(&self, other: &T) -> bool {
67 let cd1 = self
68 .cal_date_range(&ndt(1970, 1, 1), &ndt(2200, 12, 31))
69 .unwrap();
70 let cd2 = other
71 .cal_date_range(&ndt(1970, 1, 1), &ndt(2200, 12, 31))
72 .unwrap();
73 cd1.iter().zip(cd2.iter()).all(|(x, y)| {
74 self.is_bus_day(x) == other.is_bus_day(x)
75 && self.is_settlement(x) == other.is_settlement(y)
76 })
77 }
78}
79
80#[cfg(test)]
82mod tests {
83 use super::*;
84
85 fn fixture_hol_cal() -> Cal {
86 let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; Cal::new(hols, vec![5, 6])
88 }
89
90 fn fixture_hol_cal2() -> Cal {
91 let hols = vec![
92 NaiveDateTime::parse_from_str("2015-09-08 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
93 NaiveDateTime::parse_from_str("2015-09-09 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
94 ];
95 Cal::new(hols, vec![5, 6])
96 }
97
98 #[test]
99 fn test_union_cal() {
100 let cal1 = fixture_hol_cal();
101 let cal2 = fixture_hol_cal2();
102 let ucal = UnionCal::new(vec![cal1, cal2], None);
103
104 let sat =
105 NaiveDateTime::parse_from_str("2015-09-05 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
106 let next = ucal.roll_forward_bus_day(&sat);
107 assert_eq!(
108 next,
109 NaiveDateTime::parse_from_str("2015-09-10 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
110 );
111 }
112
113 #[test]
114 fn test_union_cal_with_settle() {
115 let hols = vec![
116 NaiveDateTime::parse_from_str("2015-09-08 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
117 NaiveDateTime::parse_from_str("2015-09-09 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
118 ];
119 let scal = Cal::new(hols, vec![5, 6]);
120 let cal = Cal::new(vec![], vec![5, 6]);
121 let ucal = UnionCal::new(vec![cal], vec![scal].into());
122
123 let mon =
124 NaiveDateTime::parse_from_str("2015-09-08 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
125 let next = ucal.roll_forward_bus_day(&mon);
126 assert_eq!(
127 next,
128 NaiveDateTime::parse_from_str("2015-09-08 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
129 );
130 }
131
132 #[test]
133 fn test_cross_equality() {
134 let cal = fixture_hol_cal();
135 let ucal = UnionCal::new(vec![cal.clone()], None);
136 assert_eq!(cal, ucal);
137 assert_eq!(ucal, cal);
138
139 let ucals = UnionCal::new(vec![cal.clone()], vec![cal.clone()].into());
140 assert_ne!(cal, ucals);
141 assert_ne!(ucals, cal);
142
143 let cal2 = fixture_hol_cal2();
144 assert_ne!(cal2, ucal);
145 assert_ne!(ucal, cal2);
146 }
147}