use time::Date;
use time::Duration;
use time::Month;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum Holiday {
Neujahr,
HeiligeDreiKoenige,
Frauentag,
Karfreitag,
Ostermontag,
TagDerArbeit,
TagDerBefreiung,
ChristiHimmelfahrt,
Pfingstsonntag,
Pfingstmontag,
Fronleichnam,
AugsburgerFriedensfest,
MariaeHimmelfahrt,
Weltkindertag,
TagDerDeutschenEinheit,
Reformationstag,
Allerheiligen,
BussUndBettag,
ErsterWeihnachtsfeiertag,
ZweiterWeihnachtsfeiertag,
}
impl Holiday {
pub fn date(&self, year: i32) -> Option<Date> {
match self {
Holiday::Neujahr => Date::from_calendar_date(year, Month::January, 1).ok(),
Holiday::HeiligeDreiKoenige => Date::from_calendar_date(year, Month::January, 6).ok(),
Holiday::Frauentag => Date::from_calendar_date(year, Month::March, 8).ok(),
Holiday::Karfreitag => relative_to_easter_sunday(year, -2),
Holiday::Ostermontag => relative_to_easter_sunday(year, 1),
Holiday::TagDerArbeit => Date::from_calendar_date(year, Month::May, 1).ok(),
Holiday::TagDerBefreiung => Date::from_calendar_date(year, Month::May, 8).ok(),
Holiday::ChristiHimmelfahrt => relative_to_easter_sunday(year, 39),
Holiday::Pfingstsonntag => relative_to_easter_sunday(year, 49),
Holiday::Pfingstmontag => relative_to_easter_sunday(year, 50),
Holiday::Fronleichnam => relative_to_easter_sunday(year, 60),
Holiday::AugsburgerFriedensfest => {
Date::from_calendar_date(year, Month::August, 8).ok()
}
Holiday::MariaeHimmelfahrt => Date::from_calendar_date(year, Month::August, 15).ok(),
Holiday::Weltkindertag => Date::from_calendar_date(year, Month::September, 20).ok(),
Holiday::TagDerDeutschenEinheit => {
Date::from_calendar_date(year, Month::October, 3).ok()
}
Holiday::Reformationstag => Date::from_calendar_date(year, Month::October, 31).ok(),
Holiday::Allerheiligen => Date::from_calendar_date(year, Month::November, 1).ok(),
Holiday::BussUndBettag => bus_und_bettag(year),
Holiday::ErsterWeihnachtsfeiertag => {
Date::from_calendar_date(year, Month::December, 25).ok()
}
Holiday::ZweiterWeihnachtsfeiertag => {
Date::from_calendar_date(year, Month::December, 26).ok()
}
}
}
}
fn bus_und_bettag(year: i32) -> Option<Date> {
let reference_date = Date::from_calendar_date(year, Month::November, 23).ok()?;
let weekday_ordinal = i64::from(reference_date.weekday().number_days_from_monday());
let duration_to_previous_wednesday = if weekday_ordinal < 3 {
Duration::days(-(weekday_ordinal + 5))
} else {
Duration::days(2 - weekday_ordinal)
};
Some(reference_date + duration_to_previous_wednesday)
}
fn relative_to_easter_sunday(year: i32, days_offset: i64) -> Option<Date> {
let easter_sunday = computus_gregorian(year)?;
Some(easter_sunday + Duration::days(days_offset))
}
fn computus_gregorian(year: i32) -> Option<Date> {
if !(1583..=9999).contains(&year) {
return None;
}
let aa = year % 19;
let bb = year / 100;
let cc = year % 100;
let dd = bb / 4;
let ee = bb % 4;
let ff = (bb + 8) / 25;
let gg = (bb - ff + 1) / 3;
let hh = (19 * aa + bb - dd - gg + 15) % 30;
let ii = cc / 4;
let kk = cc % 4;
let ll = (32 + 2 * ee + 2 * ii - hh - kk) % 7;
let mm = (aa + 11 * hh + 22 * ll) / 451;
let month = (hh + ll - 7 * mm + 114) / 31;
let day = (hh + ll - 7 * mm + 114) % 31 + 1;
Date::from_calendar_date(year, Month::try_from(month as u8).ok()?, day as u8).ok()
}