use crate::time::calendars::Calendar;
use std::collections::{HashMap, HashSet};
use chrono::{NaiveDate, Weekday};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Default, Debug)]
pub struct HongKong;
#[typetag::serde]
impl Calendar for HongKong {
fn 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) {
return false;
}
if is_fixed_or_observed_holiday(d, m, w) {
return false;
}
if (dd == em - 3) || (dd == em) {
return false;
}
if let Some(holidays) = SPECIAL_HOLIDAYS.get(&y)
&& holidays.contains(&(d, m))
{
return false;
}
true
}
}
lazy_static::lazy_static! {
static ref SPECIAL_HOLIDAYS: HashMap<i32, HashSet<(u32, u32)>> = {
use std::iter::FromIterator;
let mut map = HashMap::new();
map.insert(2004, HashSet::from_iter([
(22, 1), (23, 1), (24, 1), (5, 4), (26, 5), (22, 6), (29, 9), (22, 10)
]));
map.insert(2005, HashSet::from_iter([
(9, 2), (10, 2), (11, 2), (5, 4), (16, 5), (11, 6), (19, 9), (11, 10)
]));
map.insert(2006, HashSet::from_iter([
(28, 1), (29, 1), (30, 1), (31, 1), (5, 4), (5, 5), (31, 5), (7, 10), (30, 10)
]));
map.insert(2007, HashSet::from_iter([
(17, 2), (18, 2), (19, 2), (20, 2), (5, 4), (24, 5), (19, 6), (26, 9), (19, 10)
]));
map.insert(2008, HashSet::from_iter([
(7, 2), (8, 2), (9, 2), (4, 4), (12, 5), (9, 6), (15, 9), (7, 10)
]));
map.insert(2009, HashSet::from_iter([
(26, 1), (27, 1), (28, 1), (4, 4), (2, 5), (28, 5), (3, 10), (26, 10)
]));
map.insert(2010, HashSet::from_iter([
(15, 2), (16, 2), (6, 4), (21, 5), (16, 6), (23, 9)
]));
map.insert(2011, HashSet::from_iter([
(3, 2), (4, 2), (5, 4), (10, 5), (6, 6), (13, 9), (5, 10), (27, 12)
]));
map.insert(2012, HashSet::from_iter([
(23, 1), (24, 1), (25, 1), (4, 4), (10, 5), (1, 10), (23, 10)
]));
map.insert(2013, HashSet::from_iter([
(11, 2), (12, 2), (13, 2), (4, 4), (17, 5), (12, 6), (20, 9), (14, 10)
]));
map.insert(2014, HashSet::from_iter([
(31, 1), (1, 2), (2, 2), (6, 5), (2, 6), (9, 9), (2, 10)
]));
map.insert(2015, HashSet::from_iter([
(19, 2), (20, 2), (7, 4), (25, 5), (20, 6), (3, 9), (28, 9), (21, 10)
]));
map.insert(2016, HashSet::from_iter([
(8, 2), (9, 2), (10, 2), (4, 4), (9, 6), (16, 9), (10, 10), (27, 12)
]));
map.insert(2017, HashSet::from_iter([
(30, 1), (31, 1), (4, 4), (3, 5), (30, 5), (5, 10)
]));
map.insert(2018, HashSet::from_iter([
(16, 2), (19, 2), (5, 4), (22, 5), (18, 6), (25, 9), (17, 10)
]));
map.insert(2019, HashSet::from_iter([
(5, 2), (6, 2), (7, 2), (5, 4), (7, 6), (7, 10)
]));
map.insert(2020, HashSet::from_iter([
(27, 1), (28, 1), (4, 4), (30, 4), (25, 6), (2, 10), (26, 10)
]));
map.insert(2021, HashSet::from_iter([
(12, 2), (15, 2), (5, 4), (19, 5), (14, 6), (22, 9), (14, 10)
]));
map.insert(2022, HashSet::from_iter([
(1, 2), (2, 2), (3, 2), (5, 4), (9, 5), (3, 6), (12, 9), (4, 10)
]));
map.insert(2023, HashSet::from_iter([
(23, 1), (24, 1), (25, 1), (5, 4), (26, 5), (22, 6), (23, 10)
]));
map.insert(2024, HashSet::from_iter([
(12, 2), (13, 2), (4, 4), (15, 5), (10, 6), (18, 9), (11, 10)
]));
map.insert(2025, HashSet::from_iter([
(29, 1), (30, 1), (31, 1), (4, 4), (5, 5), (7, 10), (29, 10)
]));
map
};
}
fn is_fixed_or_observed_holiday(d: u32, m: u32, w: Weekday) -> bool {
matches!(
(d, m, w),
(1, 1, _) | (2, 1, Weekday::Mon)
| (1, 5, _) | (2, 5, Weekday::Mon)
| (1, 7, _) | (2, 7, Weekday::Mon)
| (1, 10, _) | (2, 10, Weekday::Mon)
| (25, 12, _) | (26, 12, _)
)
}
#[cfg(test)]
mod tests {
use super::HongKong;
use crate::time::calendars::Calendar;
use chrono::{Duration, NaiveDate};
#[test]
fn test_hongkong_holiday() {
let expected_results_for_2023 = vec![
false, false, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, false, false, false, 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, true, true, true, true, true, false, false, true, true, false,
true, false, 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,
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, 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, 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, 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, false, 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[n as usize];
assert_eq!(HongKong.is_business_day(target_date), expected);
}
}
}