use crate::HolidayCalendar;
use ::chrono::Datelike;
fn check_newyear<T: Datelike>(date: &T) -> bool {
date.day() == 1 && date.month() == 1
}
fn check_epiphany<T: Datelike>(date: &T) -> bool {
date.day() == 6 && date.month() == 1
}
fn check_intwomansday<T: Datelike>(date: &T) -> bool {
date.day() == 8 && date.month() == 3
}
fn check_goodfriday<T: Datelike, T2: Datelike>(date: &T, easter_sunday: &T2) -> bool {
date.num_days_from_ce() == easter_sunday.num_days_from_ce() - 2
}
fn check_eastermonday<T: Datelike, T2: Datelike>(date: &T, easter_sunday: &T2) -> bool {
date.num_days_from_ce() == easter_sunday.num_days_from_ce() + 1
}
fn check_labourday<T: Datelike>(date: &T) -> bool {
date.day() == 1 && date.month() == 5
}
fn check_ascensionday<T: Datelike, T2: Datelike>(date: &T, easter_sunday: &T2) -> bool {
date.num_days_from_ce() == easter_sunday.num_days_from_ce() + 39
}
fn check_whitmonday<T: Datelike, T2: Datelike>(date: &T, easter_sunday: &T2) -> bool {
date.num_days_from_ce() == easter_sunday.num_days_from_ce() + 50
}
fn check_corpuschristi<T: Datelike, T2: Datelike>(date: &T, easter_sunday: &T2) -> bool {
date.num_days_from_ce() == easter_sunday.num_days_from_ce() + 60
}
fn check_assumptionday<T: Datelike>(date: &T) -> bool {
date.day() == 15 && date.month() == 8
}
fn check_childrenday<T: Datelike>(date: &T) -> bool {
date.day() == 20 && date.month() == 9
}
fn check_unityday<T: Datelike>(date: &T) -> bool {
date.day() == 3 && date.month() == 10
}
fn check_reformationday<T: Datelike>(date: &T) -> bool {
date.day() == 31 && date.month() == 10
}
fn check_allsaintsday<T: Datelike>(date: &T) -> bool {
date.day() == 1 && date.month() == 11
}
fn check_repentence<T: Datelike>(date: &T) -> bool {
let november_22 = chrono::NaiveDate::from_ymd_opt(date.year(), 11, 22)
.expect("22 Nov should exist every year");
november_22
.iter_days()
.rev()
.take(7)
.find(|d| d.weekday() == chrono::Weekday::Wed)
.expect("One of the last 7 days must be a Wednesday")
.num_days_from_ce()
== date.num_days_from_ce()
}
fn check_christmasday<T: Datelike>(date: &T) -> bool {
date.day() == 25 && date.month() == 12
}
fn check_2ndchristmasday<T: Datelike>(date: &T) -> bool {
date.day() == 26 && date.month() == 12
}
#[derive(Clone, Copy, Debug)]
enum GermanStateHoliday {
NewYearsDay,
Epiphany,
InternationalWomensDay,
GoodFriday,
EasterMonday,
LabourDay,
AscensionDay,
WhitMonday,
CorpusChristi,
AssumptionDay,
WorldChildrensDay,
GermanUnityDay,
ReformationDay,
AllSaintsDay,
RepentanceAndPrayerDay,
ChristmasDay,
SecondDayOfChristmas,
}
impl GermanStateHoliday {
fn from_date<T: Datelike>(date: T) -> Option<Self> {
let easter_sunday = crate::easter::easter_naive_date(date.year()).ok()?;
if check_newyear(&date) {
Some(Self::NewYearsDay)
} else if check_epiphany(&date) {
Some(Self::Epiphany)
} else if check_intwomansday(&date) {
Some(Self::InternationalWomensDay)
} else if check_goodfriday(&date, &easter_sunday) {
Some(Self::GoodFriday)
} else if check_eastermonday(&date, &easter_sunday) {
Some(Self::EasterMonday)
} else if check_labourday(&date) {
Some(Self::LabourDay)
} else if check_ascensionday(&date, &easter_sunday) {
Some(Self::AscensionDay)
} else if check_whitmonday(&date, &easter_sunday) {
Some(Self::WhitMonday)
} else if check_corpuschristi(&date, &easter_sunday) {
Some(Self::CorpusChristi)
} else if check_assumptionday(&date) {
Some(Self::AssumptionDay)
} else if check_childrenday(&date) {
Some(Self::WorldChildrensDay)
} else if check_unityday(&date) {
Some(Self::GermanUnityDay)
} else if check_reformationday(&date) {
Some(Self::ReformationDay)
} else if check_allsaintsday(&date) {
Some(Self::AllSaintsDay)
} else if check_repentence(&date) {
Some(Self::RepentanceAndPrayerDay)
} else if check_christmasday(&date) {
Some(Self::ChristmasDay)
} else if check_2ndchristmasday(&date) {
Some(Self::SecondDayOfChristmas)
} else {
None
}
}
fn holidays() -> [GermanStateHoliday; 17] {
use GermanStateHoliday::*;
[
NewYearsDay, Epiphany, InternationalWomensDay, GoodFriday, EasterMonday, LabourDay, AscensionDay, WhitMonday, CorpusChristi, AssumptionDay, WorldChildrensDay, GermanUnityDay, ReformationDay, AllSaintsDay, RepentanceAndPrayerDay, ChristmasDay, SecondDayOfChristmas, ]
}
}
#[derive(Clone, Copy, Debug)]
pub enum GermanState {
BW,
BY,
BE,
BB,
HB,
HH,
HE,
MV,
NI,
NW,
RP,
SL,
SN,
ST,
SH,
TH,
ANY,
}
impl GermanState {
pub fn num_holidays(&self, year: i32) -> u32 {
GermanStateHoliday::holidays()
.into_iter()
.map(|holiday| self.has_holiday(holiday, year) as u32)
.sum()
}
const fn has_holiday(&self, holiday: GermanStateHoliday, year: i32) -> bool {
match (self, holiday) {
_ if year < 1990 => false, (_, GermanStateHoliday::NewYearsDay) => true,
(GermanState::BW | GermanState::BY | GermanState::ST, GermanStateHoliday::Epiphany) => {
true
}
(GermanState::BE, GermanStateHoliday::InternationalWomensDay) if year >= 2019 => true, (GermanState::MV, GermanStateHoliday::InternationalWomensDay) if year >= 2023 => true, (
_,
GermanStateHoliday::GoodFriday
| GermanStateHoliday::EasterMonday
| GermanStateHoliday::LabourDay
| GermanStateHoliday::AscensionDay
| GermanStateHoliday::WhitMonday,
) => true,
(
GermanState::BW
| GermanState::BY
| GermanState::HE
| GermanState::NW
| GermanState::RP
| GermanState::SL,
GermanStateHoliday::CorpusChristi,
) => true,
(GermanState::BY | GermanState::SL, GermanStateHoliday::AssumptionDay) => true,
(GermanState::TH, GermanStateHoliday::WorldChildrensDay) if year >= 2019 => true,
(_, GermanStateHoliday::GermanUnityDay) => true,
(_, GermanStateHoliday::ReformationDay) if year == 2017 => true, (
GermanState::BB
| GermanState::MV
| GermanState::SN
| GermanState::ST
| GermanState::TH,
GermanStateHoliday::ReformationDay,
) => true, (
GermanState::HB | GermanState::HH | GermanState::NI | GermanState::SH,
GermanStateHoliday::ReformationDay,
) if year >= 2018 => true, (
GermanState::BW
| GermanState::BY
| GermanState::NW
| GermanState::RP
| GermanState::SL,
GermanStateHoliday::AllSaintsDay,
) => true,
(_, GermanStateHoliday::RepentanceAndPrayerDay) if year <= 1994 => true, (GermanState::SN, GermanStateHoliday::RepentanceAndPrayerDay) => true,
(_, GermanStateHoliday::ChristmasDay | GermanStateHoliday::SecondDayOfChristmas) => {
true
}
(
GermanState::ANY,
GermanStateHoliday::InternationalWomensDay | GermanStateHoliday::WorldChildrensDay,
) if year >= 2019 => true, (
GermanState::ANY,
GermanStateHoliday::Epiphany
| GermanStateHoliday::CorpusChristi
| GermanStateHoliday::AssumptionDay
| GermanStateHoliday::ReformationDay
| GermanStateHoliday::AllSaintsDay
| GermanStateHoliday::RepentanceAndPrayerDay,
) => true, _ => false,
}
}
}
#[test]
fn test_number_of_holidays() {
let tests = [
(GermanState::BW, 12),
(GermanState::BY, 13),
(GermanState::BE, 10),
(GermanState::BB, 10),
(GermanState::HB, 10),
(GermanState::HH, 10),
(GermanState::HE, 10),
(GermanState::MV, 11),
(GermanState::NI, 10),
(GermanState::NW, 11),
(GermanState::RP, 11),
(GermanState::SL, 12),
(GermanState::SN, 11),
(GermanState::ST, 11),
(GermanState::SH, 10),
(GermanState::TH, 11),
];
for (state, num) in tests {
assert_eq!(state.num_holidays(2023), num);
}
}
impl<T: Datelike + Copy + PartialOrd> HolidayCalendar<T> for GermanState {
fn is_holiday(&self, date: T) -> bool {
if let Some(holiday) = GermanStateHoliday::from_date(date) {
self.has_holiday(holiday, date.year())
} else {
false
}
}
}
impl<T: Datelike + Copy + PartialOrd> HolidayCalendar<T> for Vec<GermanState> {
fn is_holiday(&self, date: T) -> bool {
if let Some(holiday) = GermanStateHoliday::from_date(date) {
self.iter()
.any(|state| state.has_holiday(holiday, date.year()))
} else {
false
}
}
}
#[test]
fn test_is_holiday() {
use ::chrono::NaiveDate;
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 1).expect("Valid date")),
true
);
assert_eq!(
GermanState::BW.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 6).expect("Valid date")),
true
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 6).expect("Valid date")),
true
);
assert_eq!(
GermanState::ST.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 6).expect("Valid date")),
true
);
assert_eq!(
GermanState::BE.is_holiday(NaiveDate::from_ymd_opt(2020, 3, 8).expect("Valid date")),
true
);
assert_eq!(
GermanState::MV.is_holiday(NaiveDate::from_ymd_opt(2023, 3, 8).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 4, 7).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 4, 10).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 5, 1).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 5, 18).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 5, 29).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 6, 8).expect("Valid date")),
true
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2023, 8, 15).expect("Valid date")),
true
);
assert_eq!(
GermanState::SL.is_holiday(NaiveDate::from_ymd_opt(2023, 8, 15).expect("Valid date")),
true
);
assert_eq!(
GermanState::TH.is_holiday(NaiveDate::from_ymd_opt(2023, 9, 20).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 10, 3).expect("Valid date")),
true
);
assert_eq!(
GermanState::TH.is_holiday(NaiveDate::from_ymd_opt(2023, 10, 31).expect("Valid date")),
true
);
assert_eq!(
GermanState::HB.is_holiday(NaiveDate::from_ymd_opt(2023, 10, 31).expect("Valid date")),
true
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2017, 10, 31).expect("Valid date")),
true
);
assert_eq!(
GermanState::BB.is_holiday(NaiveDate::from_ymd_opt(1998, 10, 31).expect("Valid date")),
true
);
assert_eq!(
GermanState::BW.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 1).expect("Valid date")),
true
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 1).expect("Valid date")),
true
);
assert_eq!(
GermanState::SN.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 22).expect("Valid date")),
true
);
assert_eq!(
GermanState::NI.is_holiday(NaiveDate::from_ymd_opt(1993, 11, 17).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 12, 25).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 12, 26).expect("Valid date")),
true
);
assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 2, 1).expect("Valid date")),
false
); assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 7, 15).expect("Valid date")),
false
); assert_eq!(
GermanState::ANY.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 20).expect("Valid date")),
false
);
assert_eq!(
GermanState::NI.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 6).expect("Valid date")),
false
);
assert_eq!(
GermanState::RP.is_holiday(NaiveDate::from_ymd_opt(2023, 1, 6).expect("Valid date")),
false
);
assert_eq!(
GermanState::TH.is_holiday(NaiveDate::from_ymd_opt(2023, 3, 8).expect("Valid date")),
false
);
assert_eq!(
GermanState::NW.is_holiday(NaiveDate::from_ymd_opt(2023, 3, 8).expect("Valid date")),
false
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2023, 10, 31).expect("Valid date")),
false
);
assert_eq!(
GermanState::NI.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 22).expect("Valid date")),
false
);
assert_eq!(
GermanState::HH.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 1).expect("Valid date")),
false
);
assert_eq!(
GermanState::NI.is_holiday(NaiveDate::from_ymd_opt(2023, 11, 1).expect("Valid date")),
false
);
assert_eq!(
GermanState::BY.is_holiday(NaiveDate::from_ymd_opt(2023, 10, 31).expect("Valid date")),
false
); assert_eq!(
GermanState::NI.is_holiday(NaiveDate::from_ymd_opt(2023, 8, 15).expect("Valid date")),
false
); assert_eq!(
GermanState::SL.is_holiday(NaiveDate::from_ymd_opt(2023, 9, 20).expect("Valid date")),
false
); assert_eq!(
GermanState::BW.is_holiday(NaiveDate::from_ymd_opt(2023, 3, 8).expect("Valid date")),
false
); }