use crate::date::Date;
pub const SNAPSHOT_DATE: &str = "2026-05-23";
pub const COVERAGE: (i32, i32) = (2020, 2040);
#[rustfmt::skip]
pub const HOLIDAYS: &[(i32, u8, u8)] = &[
(2020, 1, 1), (2020, 1, 2), (2020, 1, 3), (2020, 1, 13), (2020, 2, 11), (2020, 2, 23), (2020, 2, 24), (2020, 3, 20), (2020, 4, 29), (2020, 5, 3), (2020, 5, 4), (2020, 5, 5), (2020, 5, 6), (2020, 7, 23), (2020, 7, 24), (2020, 8, 10), (2020, 9, 21), (2020, 9, 22), (2020, 11, 3), (2020, 11, 23), (2020, 12, 31), (2021, 1, 1), (2021, 1, 2), (2021, 1, 3), (2021, 1, 11), (2021, 2, 11), (2021, 2, 23), (2021, 3, 20), (2021, 4, 29), (2021, 5, 3), (2021, 5, 4), (2021, 5, 5), (2021, 7, 22), (2021, 7, 23), (2021, 8, 8), (2021, 8, 9), (2021, 9, 20), (2021, 9, 23), (2021, 11, 3), (2021, 11, 23), (2021, 12, 31), (2022, 1, 1), (2022, 1, 2), (2022, 1, 3), (2022, 1, 10), (2022, 2, 11), (2022, 2, 23), (2022, 3, 21), (2022, 4, 29), (2022, 5, 3), (2022, 5, 4), (2022, 5, 5), (2022, 7, 18), (2022, 8, 11), (2022, 9, 19), (2022, 9, 23), (2022, 10, 10), (2022, 11, 3), (2022, 11, 23), (2022, 12, 31), (2023, 1, 1), (2023, 1, 2), (2023, 1, 3), (2023, 1, 9), (2023, 2, 11), (2023, 2, 23), (2023, 3, 21), (2023, 4, 29), (2023, 5, 3), (2023, 5, 4), (2023, 5, 5), (2023, 7, 17), (2023, 8, 11), (2023, 9, 18), (2023, 9, 23), (2023, 10, 9), (2023, 11, 3), (2023, 11, 23), (2023, 12, 31), (2024, 1, 1), (2024, 1, 2), (2024, 1, 3), (2024, 1, 8), (2024, 2, 11), (2024, 2, 12), (2024, 2, 23), (2024, 3, 20), (2024, 4, 29), (2024, 5, 3), (2024, 5, 4), (2024, 5, 5), (2024, 5, 6), (2024, 7, 15), (2024, 8, 11), (2024, 8, 12), (2024, 9, 16), (2024, 9, 22), (2024, 9, 23), (2024, 10, 14), (2024, 11, 3), (2024, 11, 4), (2024, 11, 23), (2024, 12, 31), (2025, 1, 1), (2025, 1, 2), (2025, 1, 3), (2025, 1, 13), (2025, 2, 11), (2025, 2, 23), (2025, 2, 24), (2025, 3, 20), (2025, 4, 29), (2025, 5, 3), (2025, 5, 4), (2025, 5, 5), (2025, 5, 6), (2025, 7, 21), (2025, 8, 11), (2025, 9, 15), (2025, 9, 23), (2025, 10, 13), (2025, 11, 3), (2025, 11, 23), (2025, 11, 24), (2025, 12, 31), (2026, 1, 1), (2026, 1, 2), (2026, 1, 3), (2026, 1, 12), (2026, 2, 11), (2026, 2, 23), (2026, 3, 20), (2026, 4, 29), (2026, 5, 3), (2026, 5, 4), (2026, 5, 5), (2026, 5, 6), (2026, 7, 20), (2026, 8, 11), (2026, 9, 21), (2026, 9, 22), (2026, 9, 23), (2026, 10, 12), (2026, 11, 3), (2026, 11, 23), (2026, 12, 31), (2027, 1, 1), (2027, 1, 2), (2027, 1, 3), (2027, 1, 11), (2027, 2, 11), (2027, 2, 23), (2027, 3, 21), (2027, 3, 22), (2027, 4, 29), (2027, 5, 3), (2027, 5, 4), (2027, 5, 5), (2027, 7, 19), (2027, 8, 11), (2027, 9, 20), (2027, 9, 23), (2027, 10, 11), (2027, 11, 3), (2027, 11, 23), (2027, 12, 31), (2028, 1, 1), (2028, 1, 2), (2028, 1, 3), (2028, 1, 10), (2028, 2, 11), (2028, 2, 23), (2028, 3, 20), (2028, 4, 29), (2028, 5, 3), (2028, 5, 4), (2028, 5, 5), (2028, 7, 17), (2028, 8, 11), (2028, 9, 18), (2028, 9, 22), (2028, 10, 9), (2028, 11, 3), (2028, 11, 23), (2028, 12, 31), (2029, 1, 1), (2029, 1, 2), (2029, 1, 3), (2029, 1, 8), (2029, 2, 11), (2029, 2, 12), (2029, 2, 23), (2029, 3, 20), (2029, 4, 29), (2029, 4, 30), (2029, 5, 3), (2029, 5, 4), (2029, 5, 5), (2029, 7, 16), (2029, 8, 11), (2029, 9, 17), (2029, 9, 23), (2029, 9, 24), (2029, 10, 8), (2029, 11, 3), (2029, 11, 23), (2029, 12, 31), (2030, 1, 1), (2030, 1, 2), (2030, 1, 3), (2030, 1, 14), (2030, 2, 11), (2030, 2, 23), (2030, 3, 20), (2030, 4, 29), (2030, 5, 3), (2030, 5, 4), (2030, 5, 5), (2030, 5, 6), (2030, 7, 15), (2030, 8, 11), (2030, 8, 12), (2030, 9, 16), (2030, 9, 23), (2030, 10, 14), (2030, 11, 3), (2030, 11, 4), (2030, 11, 23), (2030, 12, 31), (2031, 1, 1), (2031, 1, 2), (2031, 1, 3), (2031, 1, 13), (2031, 2, 11), (2031, 2, 23), (2031, 2, 24), (2031, 3, 21), (2031, 4, 29), (2031, 5, 3), (2031, 5, 4), (2031, 5, 5), (2031, 5, 6), (2031, 7, 21), (2031, 8, 11), (2031, 9, 15), (2031, 9, 23), (2031, 10, 13), (2031, 11, 3), (2031, 11, 23), (2031, 11, 24), (2031, 12, 31), (2032, 1, 1), (2032, 1, 2), (2032, 1, 3), (2032, 1, 12), (2032, 2, 11), (2032, 2, 23), (2032, 3, 20), (2032, 4, 29), (2032, 5, 3), (2032, 5, 4), (2032, 5, 5), (2032, 7, 19), (2032, 8, 11), (2032, 9, 20), (2032, 9, 21), (2032, 9, 22), (2032, 10, 11), (2032, 11, 3), (2032, 11, 23), (2032, 12, 31), (2033, 1, 1), (2033, 1, 2), (2033, 1, 3), (2033, 1, 10), (2033, 2, 11), (2033, 2, 23), (2033, 3, 20), (2033, 3, 21), (2033, 4, 29), (2033, 5, 3), (2033, 5, 4), (2033, 5, 5), (2033, 7, 18), (2033, 8, 11), (2033, 9, 19), (2033, 9, 23), (2033, 10, 10), (2033, 11, 3), (2033, 11, 23), (2033, 12, 31), (2034, 1, 1), (2034, 1, 2), (2034, 1, 3), (2034, 1, 9), (2034, 2, 11), (2034, 2, 23), (2034, 3, 20), (2034, 4, 29), (2034, 5, 3), (2034, 5, 4), (2034, 5, 5), (2034, 7, 17), (2034, 8, 11), (2034, 9, 18), (2034, 9, 23), (2034, 10, 9), (2034, 11, 3), (2034, 11, 23), (2034, 12, 31), (2035, 1, 1), (2035, 1, 2), (2035, 1, 3), (2035, 1, 8), (2035, 2, 11), (2035, 2, 12), (2035, 2, 23), (2035, 3, 21), (2035, 4, 29), (2035, 4, 30), (2035, 5, 3), (2035, 5, 4), (2035, 5, 5), (2035, 7, 16), (2035, 8, 11), (2035, 9, 17), (2035, 9, 23), (2035, 9, 24), (2035, 10, 8), (2035, 11, 3), (2035, 11, 23), (2035, 12, 31), (2036, 1, 1), (2036, 1, 2), (2036, 1, 3), (2036, 1, 14), (2036, 2, 11), (2036, 2, 23), (2036, 3, 20), (2036, 4, 29), (2036, 5, 3), (2036, 5, 4), (2036, 5, 5), (2036, 5, 6), (2036, 7, 21), (2036, 8, 11), (2036, 9, 15), (2036, 9, 22), (2036, 10, 13), (2036, 11, 3), (2036, 11, 23), (2036, 11, 24), (2036, 12, 31), (2037, 1, 1), (2037, 1, 2), (2037, 1, 3), (2037, 1, 12), (2037, 2, 11), (2037, 2, 23), (2037, 3, 20), (2037, 4, 29), (2037, 5, 3), (2037, 5, 4), (2037, 5, 5), (2037, 5, 6), (2037, 7, 20), (2037, 8, 11), (2037, 9, 21), (2037, 9, 22), (2037, 9, 23), (2037, 10, 12), (2037, 11, 3), (2037, 11, 23), (2037, 12, 31), (2038, 1, 1), (2038, 1, 2), (2038, 1, 3), (2038, 1, 11), (2038, 2, 11), (2038, 2, 23), (2038, 3, 20), (2038, 4, 29), (2038, 5, 3), (2038, 5, 4), (2038, 5, 5), (2038, 7, 19), (2038, 8, 11), (2038, 9, 20), (2038, 9, 23), (2038, 10, 11), (2038, 11, 3), (2038, 11, 23), (2038, 12, 31), (2039, 1, 1), (2039, 1, 2), (2039, 1, 3), (2039, 1, 10), (2039, 2, 11), (2039, 2, 23), (2039, 3, 21), (2039, 4, 29), (2039, 5, 3), (2039, 5, 4), (2039, 5, 5), (2039, 7, 18), (2039, 8, 11), (2039, 9, 19), (2039, 9, 23), (2039, 10, 10), (2039, 11, 3), (2039, 11, 23), (2039, 12, 31), (2040, 1, 1), (2040, 1, 2), (2040, 1, 3), (2040, 1, 9), (2040, 2, 11), (2040, 2, 23), (2040, 3, 20), (2040, 4, 29), (2040, 4, 30), (2040, 5, 3), (2040, 5, 4), (2040, 5, 5), (2040, 7, 16), (2040, 8, 11), (2040, 9, 17), (2040, 9, 22), (2040, 10, 8), (2040, 11, 3), (2040, 11, 23), (2040, 12, 31), ];
#[must_use]
pub fn is_holiday(date: Date) -> bool {
let key = (date.year(), date.month(), date.day());
HOLIDAYS.binary_search(&key).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn holidays_2024_published_jpx_calendar() {
let dates = [
(1, 1), (1, 2), (1, 3), (1, 8), (2, 11), (2, 12), (2, 23), (3, 20), (4, 29), (5, 3), (5, 6), (7, 15), (8, 12), (9, 16), (9, 22), (9, 23), (10, 14), (11, 4), (11, 23), (12, 31), ];
for (m, d) in dates {
assert!(
is_holiday(Date::ymd(2024, m, d).unwrap()),
"2024-{m:02}-{d:02} must be a Japan holiday",
);
}
}
#[test]
fn non_holidays_2024_adjacent_dates() {
assert!(!is_holiday(Date::ymd(2024, 1, 4).unwrap()));
assert!(!is_holiday(Date::ymd(2024, 1, 5).unwrap()));
assert!(!is_holiday(Date::ymd(2024, 5, 2).unwrap()));
}
#[test]
fn olympic_year_2020_specials() {
assert!(is_holiday(Date::ymd(2020, 7, 23).unwrap()), "Marine 2020");
assert!(is_holiday(Date::ymd(2020, 7, 24).unwrap()), "Sports 2020");
assert!(is_holiday(Date::ymd(2020, 8, 10).unwrap()), "Mountain 2020");
assert!(!is_holiday(Date::ymd(2020, 7, 20).unwrap())); assert!(!is_holiday(Date::ymd(2020, 10, 12).unwrap())); assert!(!is_holiday(Date::ymd(2020, 8, 11).unwrap())); }
#[test]
fn olympic_year_2021_specials() {
assert!(is_holiday(Date::ymd(2021, 7, 22).unwrap()), "Marine 2021");
assert!(is_holiday(Date::ymd(2021, 7, 23).unwrap()), "Sports 2021");
assert!(is_holiday(Date::ymd(2021, 8, 8).unwrap()), "Mountain 2021");
assert!(
is_holiday(Date::ymd(2021, 8, 9).unwrap()),
"Mountain sub 2021"
);
assert!(!is_holiday(Date::ymd(2021, 7, 19).unwrap())); assert!(!is_holiday(Date::ymd(2021, 10, 11).unwrap())); assert!(!is_holiday(Date::ymd(2021, 8, 11).unwrap())); }
#[test]
fn emperors_birthday_after_2020_change() {
assert!(is_holiday(Date::ymd(2020, 2, 23).unwrap()));
assert!(is_holiday(Date::ymd(2020, 2, 24).unwrap()));
assert!(is_holiday(Date::ymd(2024, 2, 23).unwrap()));
assert!(!is_holiday(Date::ymd(2020, 12, 23).unwrap()));
assert!(!is_holiday(Date::ymd(2024, 12, 23).unwrap()));
}
#[test]
fn out_of_coverage_returns_false() {
assert!(!is_holiday(Date::ymd(2019, 1, 1).unwrap()));
assert!(!is_holiday(Date::ymd(2041, 1, 1).unwrap()));
}
#[test]
fn holidays_table_is_sorted_ascending() {
for window in HOLIDAYS.windows(2) {
let a = window[0];
let b = window[1];
assert!(a < b, "HOLIDAYS not strictly ascending: {a:?} >= {b:?}");
}
}
#[test]
fn coverage_range_is_2020_to_2040() {
assert_eq!(COVERAGE, (2020, 2040));
let first = HOLIDAYS[0];
let last = HOLIDAYS[HOLIDAYS.len() - 1];
assert!(first.0 >= COVERAGE.0);
assert!(last.0 <= COVERAGE.1);
}
#[test]
fn snapshot_date_is_iso_yyyymmdd() {
assert_eq!(SNAPSHOT_DATE.len(), 10);
assert_eq!(&SNAPSHOT_DATE[4..5], "-");
assert_eq!(&SNAPSHOT_DATE[7..8], "-");
}
#[test]
fn peoples_holiday_fires_when_respect_aged_and_equinox_sandwich() {
assert!(is_holiday(Date::ymd(2026, 9, 22).unwrap()));
assert!(is_holiday(Date::ymd(2032, 9, 21).unwrap()));
assert!(is_holiday(Date::ymd(2037, 9, 22).unwrap()));
}
}