use crate::shared::shared;
use crate::time::calendar::{Calendar, CalendarImpl};
use crate::time::date::{Date, Month, Year};
use crate::time::weekday::Weekday;
const HOLIDAY_HORIZON: Year = 2022;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Market {
Tadawul,
}
pub struct SaudiArabia;
impl SaudiArabia {
pub fn new(market: Market) -> Calendar {
let imp: crate::shared::Shared<dyn CalendarImpl> = match market {
Market::Tadawul => shared(TadawulImpl),
};
Calendar::from_impl(imp)
}
}
fn is_true_weekend(d: Date) -> bool {
let w = d.weekday();
if d < Date::new(29, Month::June, 2013) {
w == Weekday::Thursday || w == Weekday::Friday
} else {
w == Weekday::Friday || w == Weekday::Saturday
}
}
fn is_eid_al_adha(d: Date) -> bool {
const EID_AL_ADHA: &[(i32, Month, i32)] = &[
(7, Month::April, 1998),
(27, Month::March, 1999),
(16, Month::March, 2000),
(5, Month::March, 2001),
(23, Month::February, 2002),
(12, Month::February, 2003),
(1, Month::February, 2004),
(21, Month::January, 2005),
(10, Month::January, 2006),
(31, Month::December, 2006),
(20, Month::December, 2007),
(8, Month::December, 2008),
(27, Month::November, 2009),
(16, Month::November, 2010),
(6, Month::November, 2011),
(26, Month::October, 2012),
(15, Month::October, 2013),
(4, Month::October, 2014),
(24, Month::September, 2015),
(11, Month::September, 2016),
(1, Month::September, 2017),
(23, Month::August, 2018),
(12, Month::August, 2019),
(31, Month::July, 2020),
(20, Month::July, 2021),
(10, Month::July, 2022),
];
EID_AL_ADHA.iter().any(|&(day, m, y)| {
let p = Date::new(day, m, y);
d >= p - 1 && d <= p + 4
})
}
fn is_eid_al_fitr(d: Date) -> bool {
const EID_AL_FITR: &[(i32, Month, i32)] = &[
(16, Month::December, 2001),
(5, Month::December, 2002),
(25, Month::November, 2003),
(13, Month::November, 2004),
(3, Month::November, 2005),
(23, Month::October, 2006),
(12, Month::October, 2007),
(30, Month::September, 2008),
(20, Month::September, 2009),
(10, Month::September, 2010),
(30, Month::August, 2011),
(19, Month::August, 2012),
(8, Month::August, 2013),
(28, Month::July, 2014),
(17, Month::July, 2015),
(6, Month::July, 2016),
(25, Month::June, 2017),
(15, Month::June, 2018),
(4, Month::June, 2019),
(24, Month::May, 2020),
(13, Month::May, 2021),
(2, Month::May, 2022),
(21, Month::April, 2023),
(10, Month::April, 2024),
(30, Month::March, 2025),
(20, Month::March, 2026),
(9, Month::March, 2027),
(26, Month::February, 2028),
(14, Month::February, 2029),
];
EID_AL_FITR.iter().any(|&(day, m, y)| {
let p = Date::new(day, m, y);
d >= p - 1 && d <= p + 4
})
}
struct TadawulImpl;
impl CalendarImpl for TadawulImpl {
fn name(&self) -> String {
"Tadawul".to_string()
}
fn is_weekend(&self, w: Weekday) -> bool {
w == Weekday::Friday || w == Weekday::Saturday
}
fn is_weekend_on(&self, date: Date) -> bool {
is_true_weekend(date)
}
fn is_business_day(&self, date: Date) -> bool {
let d = date.day_of_month();
let m = date.month();
let y = date.year();
assert!(
y <= HOLIDAY_HORIZON,
"Saudi Arabia Islamic (Eid) holidays are tabulated only through {HOLIDAY_HORIZON} \
(matching QuantLib); year {y} is beyond the supported horizon"
);
!(is_true_weekend(date)
|| is_eid_al_adha(date)
|| is_eid_al_fitr(date)
|| (d == 23 && m == Month::September)
|| (d == 26 && m == Month::February && y == 2011)
|| (d == 19 && m == Month::March && y == 2011))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn name_matches_quantlib() {
assert_eq!(SaudiArabia::new(Market::Tadawul).name(), "Tadawul");
}
#[test]
fn national_day_is_holiday() {
let c = SaudiArabia::new(Market::Tadawul);
assert!(c.is_holiday(Date::new(23, Month::September, 2019)));
}
#[test]
fn weekend_rule() {
let c = SaudiArabia::new(Market::Tadawul);
assert!(c.is_weekend(Weekday::Friday));
assert!(c.is_weekend(Weekday::Saturday));
assert!(!c.is_weekend(Weekday::Sunday));
}
#[test]
fn in_horizon_query_works() {
let c = SaudiArabia::new(Market::Tadawul);
assert!(c.is_holiday(Date::new(23, Month::September, HOLIDAY_HORIZON)));
}
#[test]
#[should_panic(expected = "beyond the supported horizon")]
fn beyond_horizon_panics() {
let c = SaudiArabia::new(Market::Tadawul);
let _ = c.is_business_day(Date::new(1, Month::January, HOLIDAY_HORIZON + 1));
}
}