use crate::time::calendars::Calendar;
use chrono::{NaiveDate, Weekday};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
pub enum UnitedStatesMarket {
Settlement,
Libor,
NYSE,
GovernmentBond,
SOFR,
NERC,
FederalReserve,
}
#[derive(Deserialize, Serialize, Default, Debug)]
pub struct UnitedStates {
pub market: Option<UnitedStatesMarket>,
}
impl UnitedStates {
fn is_washington_birthday(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
((y >= 1971) && ((15..=21).contains(&d) && w == Weekday::Mon && m == 2))
|| ((y < 1971)
&& (d == 22 || (d == 23 && w == Weekday::Mon) || (d == 21 && w == Weekday::Fri))
&& (m == 2))
}
fn is_memorial_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
((y >= 1971) && (d >= 25 && w == Weekday::Mon && m == 5))
|| ((y < 1971)
&& ((d == 30 || (d == 31 && w == Weekday::Mon) || (d == 29 && w == Weekday::Fri))
&& m == 5))
}
fn is_labor_day(&self, date: NaiveDate) -> bool {
let (d, w, m, _y, _) = self.naive_date_to_dkmy(date);
d <= 7 && w == Weekday::Mon && m == 9
}
fn is_columbus_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
(8..=14).contains(&d) && w == Weekday::Mon && m == 10 && y >= 1971
}
fn is_veterans_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
((y <= 1970 || y >= 1978)
&& ((d == 11 || (d == 12 && w == Weekday::Mon) || (d == 10 && w == Weekday::Fri))
&& m == 11))
|| ((y > 1970 && y < 1978) && ((22..=28).contains(&d) && w == Weekday::Mon && m == 10))
}
fn is_veterans_day_no_saturday(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
((y <= 1970 || y >= 1978) && ((d == 11 || (d == 12 && w == Weekday::Mon)) && m == 11))
|| ((y > 1970 && y < 1978) && ((22..=28).contains(&d) && w == Weekday::Mon && m == 10))
}
fn is_juneteenth(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
(d == 19 || (d == 20 && w == Weekday::Mon) || (d == 18 && w == Weekday::Fri))
&& m == 6
&& y >= 2022
}
fn settlement_is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
if self.is_weekend(date)
|| ((d == 1 || (d == 2 && w == Weekday::Mon)) && m == 1)
|| (d == 31 && w == Weekday::Fri && m == 12)
|| ((15..=21).contains(&d) && w == Weekday::Mon && m == 1 && y >= 1983)
|| self.is_washington_birthday(date)
|| self.is_memorial_day(date)
|| self.is_juneteenth(date)
|| ((d == 4 || (d == 5 && w == Weekday::Mon) || (d == 3 && w == Weekday::Fri)) && m == 7)
|| self.is_labor_day(date)
|| self.is_columbus_day(date)
|| self.is_veterans_day(date)
|| ((22..=28).contains(&d) && w == Weekday::Thu && m == 11)
|| ((d == 25 || (d == 26 && w == Weekday::Mon) ||
(d == 24 && w == Weekday::Fri)) && m == 12)
{
false
} else {
true
}
}
fn libor_is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _) = self.naive_date_to_dkmy(date);
if ((d == 5 && w == Weekday::Mon) || (d == 3 && w == Weekday::Fri)) && m == 7 && y >= 2015 {
return true;
}
self.settlement_is_business_day(date)
}
fn nyse_is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, dd) = self.naive_date_to_dkmy(date);
let em = self.easter_monday(y);
if self.is_weekend(date)
|| ((d == 1 || (d == 2 && w == Weekday::Mon)) && m == 1)
|| self.is_washington_birthday(date)
|| (dd == em-3)
|| self.is_memorial_day(date)
|| self.is_juneteenth(date)
|| ((d == 4 || (d == 5 && w == Weekday::Mon) || (d == 3 && w == Weekday::Fri)) && m == 7)
|| self.is_labor_day(date)
|| ((22..=28).contains(&d) && w == Weekday::Thu && m == 11)
|| ((d == 25 || (d == 26 && w == Weekday::Mon) || (d == 24 && w == Weekday::Fri)) && m == 12)
{
return false;
}
if y >= 1998 && (15..=21).contains(&d) && w == Weekday::Mon && m == 1 {
return false;
}
if (y <= 1968 || (y <= 1980 && y % 4 == 0)) && m == 11 && d <= 7 && w == Weekday::Tue {
return false;
}
if
(y == 2018 && m == 12 && d == 5)
|| (y == 2012 && m == 10 && (d == 29 || d == 30))
|| (y == 2007 && m == 1 && d == 2)
|| (y == 2004 && m == 6 && d == 11)
|| (y == 2001 && m == 9 && (11..=14).contains(&d))
|| (y == 1994 && m == 4 && d == 27)
|| (y == 1985 && m == 9 && d == 27)
|| (y == 1977 && m == 7 && d == 14)
|| (y == 1973 && m == 1 && d == 25)
|| (y == 1972 && m == 12 && d == 28)
|| (y == 1969 && m == 7 && d == 21)
|| (y == 1969 && m == 3 && d == 31)
|| (y == 1969 && m == 2 && d == 10)
|| (y == 1968 && m == 7 && d == 5)
|| (y == 1968 && dd >= 163 && w == Weekday::Wed)
|| (y == 1968 && m == 4 && d == 9)
|| (y == 1963 && m == 11 && d == 25)
|| (y == 1961 && m == 5 && d == 29)
|| (y == 1958 && m == 12 && d == 26)
|| ((y == 1954 || y == 1956 || y == 1965)
&& m == 12 && d == 24)
{
return false;
}
true
}
fn government_bond_is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, dd) = self.naive_date_to_dkmy(date);
let em = self.easter_monday(y);
if self.is_weekend(date)
|| ((d == 1 || (d == 2 && w == Weekday::Mon)) && m == 1)
|| ((15..=21).contains(&d) && w == Weekday::Mon && m == 1 && y >= 1983)
|| self.is_washington_birthday(date)
|| (dd == em-3&& (y < 1996 || d > 7))
|| self.is_memorial_day(date)
|| self.is_juneteenth(date)
|| ((d == 4 || (d == 5 && w == Weekday::Mon) || (d == 3 && w == Weekday::Fri)) && m == 7)
|| self.is_labor_day(date)
|| self.is_columbus_day(date)
|| self.is_veterans_day_no_saturday(date)
|| ((22..=28).contains(&d) && w == Weekday::Thu && m == 11)
|| ((d == 25 || (d == 26 && w == Weekday::Mon) || (d == 24 && w == Weekday::Fri)) && m == 12)
{
return false;
}
if
(y == 2018 && d == 5 && m == 12)
|| (y == 2025 && d == 9 && m == 1)
|| (y == 2012 && m == 10 && d == 30)
|| (y == 2004 && m == 6 && d == 11)
{
return false;
}
true
}
fn sofr_is_business_day(&self, date: NaiveDate) -> bool {
let (_, _, _, y, dd) = self.naive_date_to_dkmy(date);
if dd == (self.easter_monday(y) - 3) {
false
} else {
self.government_bond_is_business_day(date)
}
}
fn nerc_is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, _, _) = self.naive_date_to_dkmy(date);
if self.is_weekend(date)
|| ((d == 1 || (d == 2 && w == Weekday::Mon)) && m == 1)
|| self.is_memorial_day(date)
|| ((d == 4 || (d == 5 && w == Weekday::Mon)) && m == 7)
|| self.is_labor_day(date)
|| ((22..=28).contains(&d) && w == Weekday::Thu && m == 11)
|| ((d == 25 || (d == 26 && w == Weekday::Mon)) && m == 12)
{
false
} else {
true
}
}
fn federal_reserve_is_holiday(&self, date: NaiveDate) -> bool {
let (d, w, m, y, _dd) = self.naive_date_to_dkmy(date);
if self.is_weekend(date)
|| ((d == 1 || (d == 2 && w == Weekday::Mon)) && m == 1)
|| ((15..=21).contains(&d) && w == Weekday::Mon && m == 1 && y >= 1983)
|| self.is_washington_birthday(date)
|| self.is_memorial_day(date)
|| self.is_juneteenth(date)
|| ((d == 4 || (d == 5 && w == Weekday::Mon)) && m == 7)
|| self.is_labor_day(date)
|| self.is_columbus_day(date)
|| self.is_veterans_day_no_saturday(date)
|| ((22..=28).contains(&d) && w == Weekday::Thu && m == 11)
|| ((d == 25 || (d == 26 && w == Weekday::Mon)) && m == 12)
{
false
} else {
true
}
}
}
#[typetag::serde]
impl Calendar for UnitedStates {
fn is_business_day(&self, date: NaiveDate) -> bool {
match self.market {
Some(UnitedStatesMarket::Settlement) => self.settlement_is_business_day(date),
Some(UnitedStatesMarket::Libor) => self.libor_is_business_day(date),
Some(UnitedStatesMarket::NYSE) => self.nyse_is_business_day(date),
Some(UnitedStatesMarket::GovernmentBond) => self.government_bond_is_business_day(date),
Some(UnitedStatesMarket::SOFR) => self.sofr_is_business_day(date),
Some(UnitedStatesMarket::NERC) => self.nerc_is_business_day(date),
Some(UnitedStatesMarket::FederalReserve) => self.federal_reserve_is_holiday(date),
None => self.settlement_is_business_day(date),
}
}
}
#[cfg(test)]
mod tests {
use super::Calendar;
use super::UnitedStates;
use super::UnitedStatesMarket;
use chrono::{Duration, NaiveDate};
#[test]
fn test_us_sofr_holiday() {
let expected_results_for_2023_sofr = vec![
false, false, true, true, true, true, false, false, true, true, true, true, true,
false, false, false, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, true, false, false, false, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, false, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, false,
true, true, true, true, false, false, true, true, true, true, true, false, false, true,
true, true, true, true, false, false, false, true, true, true, true, false, false,
true, true, true, true, true, false, false, true, false, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, false, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, false, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true,
false, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, false, true, true, true, true, false, false,
];
let first_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
for n in 0i32..365 {
let target_date = first_date + Duration::try_days(n as i64).unwrap();
let expected = expected_results_for_2023_sofr[n as usize];
assert_eq!(
UnitedStates {
market: Some(UnitedStatesMarket::SOFR)
}
.is_business_day(target_date),
expected
);
}
let expected_results_for_2024_sofr = vec![
false, true, true, true, true, false, false, true, true, true, true, true, false,
false, false, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, false, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, true, false, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, false, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, false, true, true, false, false, true,
true, true, true, true, false, false, true, true, true, false, true, false, false,
true, true, true, true, true, false, false, true, true, true, true, true, false, false,
true, true, true, true, true, false, false, true, true, true, true, true, false, false,
true, true, true, true, true, false, false, true, true, true, true, true, false, false,
true, true, true, true, true, false, false, true, true, true, true, true, false, false,
false, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, false, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, true, true, true, true, true,
false, false, false, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, false, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, true, true, false, true, true, false, false, true, true,
];
let first_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
for n in 0i32..365 {
let target_date = first_date + Duration::try_days(n as i64).unwrap();
let expected = expected_results_for_2024_sofr[n as usize];
assert_eq!(
UnitedStates {
market: Some(UnitedStatesMarket::SOFR)
}
.is_business_day(target_date),
expected
);
}
}
}