rateslib/scheduling/calendars/
named_cal.rs1use chrono::prelude::*;
2use pyo3::exceptions::PyValueError;
3use pyo3::{pyclass, PyErr};
4use serde::{Deserialize, Serialize};
5
6use crate::scheduling::{Cal, CalendarAdjustment, DateRoll, UnionCal};
7
8#[pyclass(module = "rateslib.rs")]
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[serde(from = "NamedCalDataModel")]
12pub struct NamedCal {
13 pub name: String,
14 #[serde(skip)]
15 pub union_cal: UnionCal,
16}
17
18#[derive(Deserialize)]
19struct NamedCalDataModel {
20 name: String,
21}
22
23impl std::convert::From<NamedCalDataModel> for NamedCal {
24 fn from(model: NamedCalDataModel) -> Self {
25 Self::try_new(&model.name).expect("NamedCal data model contains bad data.")
26 }
27}
28
29impl NamedCal {
30 pub fn try_new(name: &str) -> Result<Self, PyErr> {
45 let name_ = name.to_lowercase();
46 let parts: Vec<&str> = name_.split("|").collect();
47 if parts.len() > 2 {
48 Err(PyValueError::new_err(
49 "Cannot use more than one pipe ('|') operator in `name`.",
50 ))
51 } else if parts.len() == 1 {
52 let cals: Vec<Cal> = parse_cals(parts[0])?;
53 Ok(Self {
54 name: name_,
55 union_cal: UnionCal {
56 calendars: cals,
57 settlement_calendars: None,
58 },
59 })
60 } else {
61 let cals: Vec<Cal> = parse_cals(parts[0])?;
62 let settle_cals: Vec<Cal> = parse_cals(parts[1])?;
63 Ok(Self {
64 name: name_,
65 union_cal: UnionCal {
66 calendars: cals,
67 settlement_calendars: Some(settle_cals),
68 },
69 })
70 }
71 }
72}
73
74impl DateRoll for NamedCal {
75 fn is_weekday(&self, date: &NaiveDateTime) -> bool {
76 self.union_cal.is_weekday(date)
77 }
78
79 fn is_holiday(&self, date: &NaiveDateTime) -> bool {
80 self.union_cal.is_holiday(date)
81 }
82
83 fn is_settlement(&self, date: &NaiveDateTime) -> bool {
84 self.union_cal.is_settlement(date)
85 }
86}
87
88impl CalendarAdjustment for NamedCal {}
89
90fn parse_cals(name: &str) -> Result<Vec<Cal>, PyErr> {
91 let mut cals: Vec<Cal> = Vec::new();
92 for cal in name.split(",") {
93 cals.push(Cal::try_from_name(cal)?)
94 }
95 Ok(cals)
96}
97
98impl<T> PartialEq<T> for NamedCal
99where
100 T: DateRoll,
101{
102 fn eq(&self, other: &T) -> bool {
103 self.union_cal.eq(other)
104 }
105}
106
107#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::scheduling::ndt;
112
113 #[test]
114 fn test_named_cal() {
115 let ncal = NamedCal::try_new("tgt,nyc").unwrap();
116
117 assert!(ncal.is_non_bus_day(&ndt(1970, 2, 16))); assert!(ncal.is_non_bus_day(&ndt(1970, 5, 1))); assert!(ncal.is_bus_day(&ndt(1970, 2, 17)));
120 }
121
122 #[test]
123 fn test_named_cal_pipe() {
124 let ncal = NamedCal::try_new("tgt,nyc|ldn").unwrap();
125
126 assert!(ncal.is_non_bus_day(&ndt(1970, 2, 16))); assert!(ncal.is_non_bus_day(&ndt(1970, 5, 1))); assert!(ncal.is_bus_day(&ndt(1970, 2, 17)));
129
130 assert!(!ncal.is_settlement(&ndt(1970, 5, 4))); assert!(ncal.is_settlement(&ndt(1970, 5, 1))); }
133
134 #[test]
135 fn test_named_cal_error() {
136 let ncal = NamedCal::try_new("tgt,nyc|ldn|");
137 assert!(ncal.is_err());
138
139 let ncal = NamedCal::try_new("");
140 assert!(ncal.is_err());
141 }
142}