use chrono::{Datelike, NaiveDate};
use computus;
use std::i32::MAX as I32_MAX;
pub fn holidays_in_year(y: i32) -> Vec<NaiveDate> {
let mut hiy: Vec<NaiveDate> = vec![];
for (sh_sy, sh_ty, sh_m, sh_d) in STATIC_HOLIDAYS {
if y >= *sh_sy && y <= *sh_ty {
hiy.push(NaiveDate::from_ymd(y, *sh_m, *sh_d));
}
}
hiy.append(&mut get_easter_holidays(y));
hiy.sort();
hiy
}
pub fn holidays_within(from: &NaiveDate, to: &NaiveDate) -> Vec<NaiveDate> {
let mut hir = vec![];
for y in from.year()..to.year() + 1 {
hir.append(&mut holidays_in_year(y))
}
hir.retain(|holiday| holiday >= from && holiday <= to);
hir
}
pub fn is_holiday(date: &NaiveDate) -> bool {
is_static_holiday(date) || is_easter_holiday(date)
}
pub fn is_holiday_ymd(y: i32, m: u32, d: u32) -> bool {
is_holiday(&NaiveDate::from_ymd(y, m, d))
}
const STATIC_HOLIDAYS: &'static [(i32, i32, u32, u32)] = &[
(1951, I32_MAX, 1, 1),
(1951, I32_MAX, 5, 1),
(1992, I32_MAX, 5, 8),
(1990, I32_MAX, 7, 5),
(1990, I32_MAX, 7, 6),
(2000, I32_MAX, 9, 28),
(1988, I32_MAX, 10, 28),
(2000, I32_MAX, 11, 17),
(1990, I32_MAX, 12, 24),
(1951, I32_MAX, 12, 25),
(1951, I32_MAX, 12, 26),
(1947, 1991, 5, 9),
];
fn is_static_holiday(date: &NaiveDate) -> bool {
let (y, m, d) = (date.year(), date.month(), date.day());
for (sh_sy, sh_ty, sh_m, sh_d) in STATIC_HOLIDAYS {
if y >= *sh_sy && y <= *sh_ty && m == *sh_m && d == *sh_d {
return true;
}
}
false
}
fn is_easter_holiday(date: &NaiveDate) -> bool {
get_easter_holidays(date.year()).contains(date)
}
fn get_easter_holidays(year: i32) -> Vec<NaiveDate> {
vec![
get_easter_holiday_friday(year),
get_easter_holiday_monday(year),
]
.into_iter()
.filter(|opt| opt.is_some())
.map(|opt| opt.expect("Date is None even though guarded by is_some filter"))
.collect()
}
fn get_easter_holiday_monday(year: i32) -> Option<NaiveDate> {
if year >= 1951 {
get_easter_sunday(year).map(|es| es.succ())
} else {
None
}
}
fn get_easter_holiday_friday(year: i32) -> Option<NaiveDate> {
if year >= 2016 {
get_easter_sunday(year).map(|es| es.pred().pred())
} else {
None
}
}
fn get_easter_sunday(year: i32) -> Option<NaiveDate> {
let easter_sunday = computus::gregorian(year);
match easter_sunday {
Ok(es) => Some(NaiveDate::from_ymd(es.year, es.month, es.day)),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_holiday_ymd() {
assert!(!is_holiday_ymd(1918, 10, 28));
assert!(is_holiday_ymd(2018, 10, 28));
assert!(!is_holiday_ymd(1999, 9, 28));
assert!(is_holiday_ymd(2000, 9, 28));
assert!(is_holiday_ymd(3000, 9, 28));
assert!(is_holiday_ymd(2018, 4, 2));
assert!(is_holiday_ymd(2018, 3, 30));
assert!(is_holiday_ymd(2018, 4, 2));
assert!(is_holiday_ymd(2018, 3, 30));
assert!(is_holiday_ymd(2015, 4, 6));
assert!(!is_holiday_ymd(2015, 4, 3));
}
#[test]
fn test_holidays_within() {
let from = NaiveDate::from_ymd(2015, 12, 1);
let to = NaiveDate::from_ymd(2017, 4, 16);
assert_eq!(
holidays_within(&from, &to).as_slice(),
&[
NaiveDate::from_ymd(2015, 12, 24),
NaiveDate::from_ymd(2015, 12, 25),
NaiveDate::from_ymd(2015, 12, 26),
NaiveDate::from_ymd(2016, 1, 1),
NaiveDate::from_ymd(2016, 3, 25),
NaiveDate::from_ymd(2016, 3, 28),
NaiveDate::from_ymd(2016, 5, 1),
NaiveDate::from_ymd(2016, 5, 8),
NaiveDate::from_ymd(2016, 7, 5),
NaiveDate::from_ymd(2016, 7, 6),
NaiveDate::from_ymd(2016, 9, 28),
NaiveDate::from_ymd(2016, 10, 28),
NaiveDate::from_ymd(2016, 11, 17),
NaiveDate::from_ymd(2016, 12, 24),
NaiveDate::from_ymd(2016, 12, 25),
NaiveDate::from_ymd(2016, 12, 26),
NaiveDate::from_ymd(2017, 1, 1),
NaiveDate::from_ymd(2017, 4, 14),
]
);
}
}