use crate::shared::shared;
use crate::time::calendar::{Calendar, CalendarImpl, is_weekend_sat_sun, western_easter_monday};
use crate::time::date::{Date, Month};
use crate::time::weekday::Weekday;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Market {
Settlement,
Nyse,
GovernmentBond,
Nerc,
LiborImpact,
FederalReserve,
Sofr,
}
pub struct UnitedStates;
impl UnitedStates {
pub fn new(market: Market) -> Calendar {
let imp: crate::shared::Shared<dyn CalendarImpl> = match market {
Market::Settlement => shared(SettlementImpl),
Market::Nyse => shared(NyseImpl),
Market::GovernmentBond => shared(GovernmentBondImpl),
Market::Nerc => shared(NercImpl),
Market::LiborImpact => shared(LiborImpactImpl),
Market::FederalReserve => shared(FederalReserveImpl),
Market::Sofr => shared(SofrImpl),
};
Calendar::from_impl(imp)
}
}
fn is_washington_birthday(d: i32, m: Month, y: i32, w: Weekday) -> bool {
if y >= 1971 {
(d >= 15 && d <= 21) && w == Weekday::Monday && m == Month::February
} else {
(d == 22 || (d == 23 && w == Weekday::Monday) || (d == 21 && w == Weekday::Friday))
&& m == Month::February
}
}
fn is_memorial_day(d: i32, m: Month, y: i32, w: Weekday) -> bool {
if y >= 1971 {
d >= 25 && w == Weekday::Monday && m == Month::May
} else {
(d == 30 || (d == 31 && w == Weekday::Monday) || (d == 29 && w == Weekday::Friday))
&& m == Month::May
}
}
fn is_labor_day(d: i32, m: Month, _y: i32, w: Weekday) -> bool {
d <= 7 && w == Weekday::Monday && m == Month::September
}
fn is_columbus_day(d: i32, m: Month, y: i32, w: Weekday) -> bool {
(d >= 8 && d <= 14) && w == Weekday::Monday && m == Month::October && y >= 1971
}
fn is_veterans_day(d: i32, m: Month, y: i32, w: Weekday) -> bool {
if y <= 1970 || y >= 1978 {
(d == 11 || (d == 12 && w == Weekday::Monday) || (d == 10 && w == Weekday::Friday))
&& m == Month::November
} else {
(d >= 22 && d <= 28) && w == Weekday::Monday && m == Month::October
}
}
fn is_veterans_day_no_saturday(d: i32, m: Month, y: i32, w: Weekday) -> bool {
if y <= 1970 || y >= 1978 {
(d == 11 || (d == 12 && w == Weekday::Monday)) && m == Month::November
} else {
(d >= 22 && d <= 28) && w == Weekday::Monday && m == Month::October
}
}
fn is_juneteenth(d: i32, m: Month, y: i32, w: Weekday, move_to_friday: bool) -> bool {
(d == 19
|| (d == 20 && w == Weekday::Monday)
|| ((d == 18 && w == Weekday::Friday) && move_to_friday))
&& m == Month::June
&& y >= 2022
}
fn settlement_is_business_day(date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let m = date.month();
let y = date.year();
!(is_weekend_sat_sun(w)
|| ((d == 1 || (d == 2 && w == Weekday::Monday)) && m == Month::January)
|| (d == 31 && w == Weekday::Friday && m == Month::December)
|| ((d >= 15 && d <= 21) && w == Weekday::Monday && m == Month::January && y >= 1983)
|| is_washington_birthday(d, m, y, w)
|| is_memorial_day(d, m, y, w)
|| is_juneteenth(d, m, y, w, true)
|| ((d == 4 || (d == 5 && w == Weekday::Monday) || (d == 3 && w == Weekday::Friday))
&& m == Month::July)
|| is_labor_day(d, m, y, w)
|| is_columbus_day(d, m, y, w)
|| is_veterans_day(d, m, y, w)
|| ((d >= 22 && d <= 28) && w == Weekday::Thursday && m == Month::November)
|| ((d == 25 || (d == 26 && w == Weekday::Monday) || (d == 24 && w == Weekday::Friday))
&& m == Month::December))
}
fn government_bond_is_business_day(date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let dd = date.day_of_year();
let m = date.month();
let y = date.year();
let em = western_easter_monday(y);
if is_weekend_sat_sun(w)
|| ((d == 1 || (d == 2 && w == Weekday::Monday)) && m == Month::January)
|| ((d >= 15 && d <= 21) && w == Weekday::Monday && m == Month::January && y >= 1983)
|| is_washington_birthday(d, m, y, w)
|| (dd == em - 3 && (y < 1996 || d > 7))
|| is_memorial_day(d, m, y, w)
|| is_juneteenth(d, m, y, w, true)
|| ((d == 4 || (d == 5 && w == Weekday::Monday) || (d == 3 && w == Weekday::Friday))
&& m == Month::July)
|| is_labor_day(d, m, y, w)
|| is_columbus_day(d, m, y, w)
|| is_veterans_day_no_saturday(d, m, y, w)
|| ((d >= 22 && d <= 28) && w == Weekday::Thursday && m == Month::November)
|| ((d == 25 || (d == 26 && w == Weekday::Monday) || (d == 24 && w == Weekday::Friday))
&& m == Month::December)
{
return false;
}
if
(y == 2018 && m == Month::December && d == 5)
|| (y == 2012 && m == Month::October && d == 30)
|| (y == 2004 && m == Month::June && d == 11)
{
return false;
}
true
}
struct SettlementImpl;
struct LiborImpactImpl;
struct NyseImpl;
struct GovernmentBondImpl;
struct SofrImpl;
struct NercImpl;
struct FederalReserveImpl;
impl CalendarImpl for SettlementImpl {
fn name(&self) -> String {
"US settlement".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
settlement_is_business_day(date)
}
}
impl CalendarImpl for LiborImpactImpl {
fn name(&self) -> String {
"US with Libor impact".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let m = date.month();
let y = date.year();
if ((d == 5 && w == Weekday::Monday) || (d == 3 && w == Weekday::Friday))
&& m == Month::July
&& y >= 2015
{
return true;
}
settlement_is_business_day(date)
}
}
impl CalendarImpl for NyseImpl {
fn name(&self) -> String {
"New York stock exchange".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let dd = date.day_of_year();
let m = date.month();
let y = date.year();
let em = western_easter_monday(y);
if is_weekend_sat_sun(w)
|| ((d == 1 || (d == 2 && w == Weekday::Monday)) && m == Month::January)
|| is_washington_birthday(d, m, y, w)
|| (dd == em - 3)
|| is_memorial_day(d, m, y, w)
|| is_juneteenth(d, m, y, w, true)
|| ((d == 4 || (d == 5 && w == Weekday::Monday) || (d == 3 && w == Weekday::Friday))
&& m == Month::July)
|| is_labor_day(d, m, y, w)
|| ((d >= 22 && d <= 28) && w == Weekday::Thursday && m == Month::November)
|| ((d == 25 || (d == 26 && w == Weekday::Monday) || (d == 24 && w == Weekday::Friday))
&& m == Month::December)
{
return false;
}
if y >= 1998 && (d >= 15 && d <= 21) && w == Weekday::Monday && m == Month::January {
return false;
}
if (y <= 1968 || (y <= 1980 && y % 4 == 0))
&& m == Month::November
&& d <= 7
&& w == Weekday::Tuesday
{
return false;
}
if
(y == 2025 && m == Month::January && d == 9)
|| (y == 2018 && m == Month::December && d == 5)
|| (y == 2012 && m == Month::October && (d == 29 || d == 30))
|| (y == 2007 && m == Month::January && d == 2)
|| (y == 2004 && m == Month::June && d == 11)
|| (y == 2001 && m == Month::September && (11..=14).contains(&d))
|| (y == 1994 && m == Month::April && d == 27)
|| (y == 1985 && m == Month::September && d == 27)
|| (y == 1977 && m == Month::July && d == 14)
|| (y == 1973 && m == Month::January && d == 25)
|| (y == 1972 && m == Month::December && d == 28)
|| (y == 1969 && m == Month::July && d == 21)
|| (y == 1969 && m == Month::March && d == 31)
|| (y == 1969 && m == Month::February && d == 10)
|| (y == 1968 && m == Month::July && d == 5)
|| (y == 1968 && dd >= 163 && w == Weekday::Wednesday)
|| (y == 1968 && m == Month::April && d == 9)
|| (y == 1963 && m == Month::November && d == 25)
|| (y == 1961 && m == Month::May && d == 29)
|| (y == 1958 && m == Month::December && d == 26)
|| ((y == 1954 || y == 1956 || y == 1965) && m == Month::December && d == 24)
{
return false;
}
true
}
}
impl CalendarImpl for GovernmentBondImpl {
fn name(&self) -> String {
"US government bond market".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
government_bond_is_business_day(date)
}
}
impl CalendarImpl for SofrImpl {
fn name(&self) -> String {
"SOFR fixing calendar".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let dy = date.day_of_year();
let y = date.year();
if dy == western_easter_monday(y) - 3 {
return false;
}
government_bond_is_business_day(date)
}
}
impl CalendarImpl for NercImpl {
fn name(&self) -> String {
"North American Energy Reliability Council".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let m = date.month();
let y = date.year();
!(is_weekend_sat_sun(w)
|| ((d == 1 || (d == 2 && w == Weekday::Monday)) && m == Month::January)
|| is_memorial_day(d, m, y, w)
|| ((d == 4 || (d == 5 && w == Weekday::Monday)) && m == Month::July)
|| is_labor_day(d, m, y, w)
|| ((d >= 22 && d <= 28) && w == Weekday::Thursday && m == Month::November)
|| ((d == 25 || (d == 26 && w == Weekday::Monday)) && m == Month::December))
}
}
impl CalendarImpl for FederalReserveImpl {
fn name(&self) -> String {
"Federal Reserve Bankwire System".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
is_weekend_sat_sun(w)
}
fn is_business_day(&self, date: Date) -> bool {
let w = date.weekday();
let d = date.day_of_month();
let m = date.month();
let y = date.year();
!(is_weekend_sat_sun(w)
|| ((d == 1 || (d == 2 && w == Weekday::Monday)) && m == Month::January)
|| ((d >= 15 && d <= 21) && w == Weekday::Monday && m == Month::January && y >= 1983)
|| is_washington_birthday(d, m, y, w)
|| is_memorial_day(d, m, y, w)
|| is_juneteenth(d, m, y, w, false)
|| ((d == 4 || (d == 5 && w == Weekday::Monday)) && m == Month::July)
|| is_labor_day(d, m, y, w)
|| is_columbus_day(d, m, y, w)
|| is_veterans_day_no_saturday(d, m, y, w)
|| ((d >= 22 && d <= 28) && w == Weekday::Thursday && m == Month::November)
|| ((d == 25 || (d == 26 && w == Weekday::Monday)) && m == Month::December))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_match_quantlib() {
assert_eq!(
UnitedStates::new(Market::Settlement).name(),
"US settlement"
);
assert_eq!(
UnitedStates::new(Market::LiborImpact).name(),
"US with Libor impact"
);
assert_eq!(
UnitedStates::new(Market::Nyse).name(),
"New York stock exchange"
);
assert_eq!(
UnitedStates::new(Market::GovernmentBond).name(),
"US government bond market"
);
assert_eq!(
UnitedStates::new(Market::Sofr).name(),
"SOFR fixing calendar"
);
assert_eq!(
UnitedStates::new(Market::Nerc).name(),
"North American Energy Reliability Council"
);
assert_eq!(
UnitedStates::new(Market::FederalReserve).name(),
"Federal Reserve Bankwire System"
);
}
#[test]
fn settlement_fixed_holidays() {
let c = UnitedStates::new(Market::Settlement);
assert!(c.is_holiday(Date::new(1, Month::January, 2019))); }
#[test]
fn weekend_rule() {
let c = UnitedStates::new(Market::Settlement);
assert!(c.is_weekend(Weekday::Saturday));
assert!(c.is_weekend(Weekday::Sunday));
}
}