rateslib/scheduling/calendars/
cal.rs1use chrono::prelude::*;
2use chrono::Weekday;
3use indexmap::set::IndexSet;
4use pyo3::{pyclass, PyErr};
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8use crate::scheduling::calendars::named::{get_holidays_by_name, get_weekmask_by_name};
9use crate::scheduling::{ndt, CalendarAdjustment, DateRoll, NamedCal, UnionCal};
10
11#[pyclass(module = "rateslib.rs")]
13#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
14pub struct Cal {
15 pub holidays: IndexSet<NaiveDateTime>,
17 pub week_mask: HashSet<Weekday>,
19 }
21
22impl Cal {
23 pub fn new(
34 holidays: Vec<NaiveDateTime>,
35 week_mask: Vec<u8>,
36 ) -> Self {
38 Cal {
39 holidays: IndexSet::from_iter(holidays),
40 week_mask: HashSet::from_iter(
41 week_mask.into_iter().map(|v| Weekday::try_from(v).unwrap()),
42 ),
43 }
45 }
46
47 pub fn try_from_name(name: &str) -> Result<Cal, PyErr> {
58 Ok(Cal::new(
59 get_holidays_by_name(name)?,
60 get_weekmask_by_name(name)?,
61 ))
63 }
64}
65
66impl DateRoll for Cal {
67 fn is_weekday(&self, date: &NaiveDateTime) -> bool {
68 !self.week_mask.contains(&date.weekday())
69 }
70
71 fn is_holiday(&self, date: &NaiveDateTime) -> bool {
72 self.holidays.contains(date)
73 }
74
75 fn is_settlement(&self, _date: &NaiveDateTime) -> bool {
76 true
77 }
78}
79
80impl CalendarAdjustment for Cal {}
81
82impl PartialEq<UnionCal> for Cal {
83 fn eq(&self, other: &UnionCal) -> bool {
84 let cd1 = self
85 .cal_date_range(&ndt(1970, 1, 1), &ndt(2200, 12, 31))
86 .unwrap();
87 let cd2 = other
88 .cal_date_range(&ndt(1970, 1, 1), &ndt(2200, 12, 31))
89 .unwrap();
90 cd1.iter().zip(cd2.iter()).all(|(x, y)| {
91 self.is_bus_day(x) == other.is_bus_day(x)
92 && self.is_settlement(x) == other.is_settlement(y)
93 })
94 }
95}
96
97impl PartialEq<NamedCal> for Cal {
98 fn eq(&self, other: &NamedCal) -> bool {
99 other.union_cal.eq(self)
100 }
101}
102
103#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::scheduling::Adjuster;
108
109 fn fixture_hol_cal() -> Cal {
110 let hols = vec![ndt(2015, 9, 5), ndt(2015, 9, 7)]; Cal::new(hols, vec![5, 6])
112 }
113
114 #[test]
115 fn test_is_holiday() {
116 let cal = fixture_hol_cal();
117 let hol =
118 NaiveDateTime::parse_from_str("2015-09-07 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
119 let no_hol =
120 NaiveDateTime::parse_from_str("2015-09-10 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
121 let saturday =
122 NaiveDateTime::parse_from_str("2024-01-06 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
123 assert!(cal.is_holiday(&hol)); assert!(!cal.is_holiday(&no_hol)); assert!(!cal.is_holiday(&saturday)); }
127
128 #[test]
129 fn test_is_weekday() {
130 let cal = fixture_hol_cal();
131 let hol =
132 NaiveDateTime::parse_from_str("2015-09-07 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
133 let no_hol =
134 NaiveDateTime::parse_from_str("2015-09-10 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
135 let saturday =
136 NaiveDateTime::parse_from_str("2024-01-06 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
137 let sunday =
138 NaiveDateTime::parse_from_str("2024-01-07 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
139 assert!(cal.is_weekday(&hol)); assert!(cal.is_weekday(&no_hol)); assert!(!cal.is_weekday(&saturday)); assert!(!cal.is_weekday(&sunday)); }
144
145 #[test]
146 fn test_calendar_adjust() {
147 let cal = fixture_hol_cal();
148 let result = cal.adjust(&ndt(2015, 9, 5), &Adjuster::Following {});
149 assert_eq!(ndt(2015, 9, 8), result);
150 }
151
152 #[test]
153 fn test_calendar_adjusts() {
154 let cal = fixture_hol_cal();
155 let result = cal.adjusts(
156 &vec![ndt(2015, 9, 5), ndt(2015, 9, 6)],
157 &Adjuster::Following {},
158 );
159 assert_eq!(vec![ndt(2015, 9, 8), ndt(2015, 9, 8)], result);
160 }
161
162 #[test]
165 fn test_get_cal() {
166 let result = Cal::try_from_name("bus").unwrap();
167 let expected = Cal::new(vec![], vec![5, 6]);
168 assert_eq!(result, expected);
169 }
170
171 #[test]
172 fn test_all() {
173 let cal = Cal::try_from_name("all").unwrap();
174 assert!(cal.is_bus_day(
175 &NaiveDateTime::parse_from_str("2024-11-11 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
176 ));
177 }
178
179 #[test]
180 fn test_nyc() {
181 let cal = Cal::try_from_name("nyc").unwrap();
182 assert!(cal.is_holiday(
183 &NaiveDateTime::parse_from_str("2024-11-11 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
184 ));
185 }
186
187 #[test]
188 fn test_tgt() {
189 let cal = Cal::try_from_name("tgt").unwrap();
190 assert!(cal.is_holiday(
191 &NaiveDateTime::parse_from_str("2024-05-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
192 ));
193 }
194
195 #[test]
196 fn test_ldn() {
197 let cal = Cal::try_from_name("ldn").unwrap();
198 assert!(cal.is_holiday(
199 &NaiveDateTime::parse_from_str("2024-08-26 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
200 ));
201 }
202
203 #[test]
204 fn test_stk() {
205 let cal = Cal::try_from_name("stk").unwrap();
206 assert!(cal.is_holiday(
207 &NaiveDateTime::parse_from_str("2024-06-06 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
208 ));
209 }
210
211 #[test]
212 fn test_osl() {
213 let cal = Cal::try_from_name("osl").unwrap();
214 assert!(cal.is_holiday(
215 &NaiveDateTime::parse_from_str("2024-05-17 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
216 ));
217 }
218
219 #[test]
220 fn test_zur() {
221 let cal = Cal::try_from_name("zur").unwrap();
222 assert!(cal.is_holiday(
223 &NaiveDateTime::parse_from_str("2024-08-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
224 ));
225 }
226
227 #[test]
228 fn test_tro() {
229 let cal = Cal::try_from_name("tro").unwrap();
230 assert!(cal.is_holiday(
231 &NaiveDateTime::parse_from_str("2024-09-30 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
232 ));
233 }
234
235 #[test]
236 fn test_tyo() {
237 let cal = Cal::try_from_name("tyo").unwrap();
238 assert!(cal.is_holiday(
239 &NaiveDateTime::parse_from_str("2024-1-3 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
240 ));
241 }
242
243 #[test]
244 fn test_fed() {
245 let cal = Cal::try_from_name("fed").unwrap();
246 assert!(cal.is_holiday(
247 &NaiveDateTime::parse_from_str("2024-11-11 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
248 ));
249 }
250
251 #[test]
252 fn test_get_calendar_error() {
253 match Cal::try_from_name("badname") {
254 Ok(_) => assert!(false),
255 Err(_) => assert!(true),
256 }
257 }
258
259 #[test]
260 fn test_syd() {
261 let cal = Cal::try_from_name("syd").unwrap();
262 assert!(cal.is_holiday(
263 &NaiveDateTime::parse_from_str("2022-09-22 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
264 ));
265 }
266
267 #[test]
268 fn test_wlg() {
269 let cal = Cal::try_from_name("wlg").unwrap();
270 assert!(cal.is_holiday(
271 &NaiveDateTime::parse_from_str("2034-07-07 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
272 ));
273 }
274
275 #[test]
276 fn test_mum() {
277 let cal = Cal::try_from_name("mum").unwrap();
278 assert!(cal.is_holiday(
279 &NaiveDateTime::parse_from_str("2025-01-26 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap()
280 ));
281 }
282}