diff --git a/Cargo.toml b/Cargo.toml index 80876a9..6117786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,15 @@ version = "0.1.1" edition = "2024" publish = ["kellnr"] +# [features] +# default = ["de"] +# # Includes Germany +# de = [] +# # Includes France +# fr = [] +# # Includes The United States +# us = [] + [dependencies] time = "0.3.41" strum = { version = "0.27.2", features = ["derive"] } diff --git a/src/countries/de.rs b/src/countries/de.rs index 98b6923..74ddde4 100644 --- a/src/countries/de.rs +++ b/src/countries/de.rs @@ -1,14 +1,14 @@ use time::{Date, Duration, Month, Weekday}; use crate::{ - countries::{CountryHolidays, StateList}, + countries::StateList, holiday::{Activity, HDate, Holiday}, utils::{self}, }; -pub(super) struct GermanHolidays; +pub(crate) struct GermanHolidays(pub Vec>); -#[derive(strum::Display, Hash, PartialEq, Eq)] +#[derive(Debug, strum::Display, Clone, Copy, Hash, PartialEq, Eq)] pub enum GermanState { /// Baden-Württemberg BW, @@ -52,164 +52,162 @@ impl super::StateList for GermanState { } } -impl CountryHolidays for GermanHolidays { - fn new() -> (String, Vec) { +impl GermanHolidays { + pub(crate) fn new() -> Self { use GermanState::*; use time::Month::*; - ( - GermanState::all_states_identifier(), - vec![ - // New Years Day - Holiday { - name: "Neujahrstag".to_string(), - date: HDate::Fixed(January, 1), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Epiphany - Holiday { - name: "Heilige drei Könige".to_string(), - date: HDate::Fixed(January, 6), - states: GermanState::list(&[ - (BW, &[Activity::after(1990)]), - (BY, &[Activity::after(1990)]), - (ST, &[Activity::after(1990)]), - ]), - }, - // International Women's Day - Holiday { - name: "Internationaler Frauentag".to_string(), - date: HDate::Fixed(March, 8), - states: GermanState::list(&[ - (BE, &[Activity::after(2019)]), - (MV, &[Activity::after(2023)]), - ]), - }, - // Good Friday - Holiday { - name: "Karfreitag".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Easter Monday - Holiday { - name: "Ostermontag".to_string(), - date: HDate::Calculated(utils::easter_monday), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Labour Day - Holiday { - name: "Tag der Arbeit".to_string(), - date: HDate::Fixed(May, 1), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Ascension Day - Holiday { - name: "Christi Himmelfahrt".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Whit Monday - Holiday { - name: "Pfingstmontag".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Corpus Christi - Holiday { - name: "Fronleichnam".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(59)), - states: GermanState::list(&[ - (BW, &[Activity::after(1990)]), - (BY, &[Activity::after(1990)]), - (HE, &[Activity::after(1990)]), - (NW, &[Activity::after(1990)]), - (RP, &[Activity::after(1990)]), - (SL, &[Activity::after(1990)]), - ]), - }, - // Assumption Day - Holiday { - name: "Mariä Himmelfahrt".to_string(), - date: HDate::Fixed(August, 15), - states: GermanState::list(&[ - (BY, &[Activity::after(1990)]), - (SL, &[Activity::after(1990)]), - ]), - }, - // World Children's Day - Holiday { - name: "Weltkindertag".to_string(), - date: HDate::Fixed(September, 20), - states: GermanState::list(&[(TH, &[Activity::after(2019)])]), - }, - // German Unity Day - Holiday { - name: "Tag der Deutschen Einheit".to_string(), - date: HDate::Fixed(October, 3), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Reformation Day - Holiday { - name: "Reformationstag".to_string(), - date: HDate::Fixed(October, 31), - states: GermanState::list(&[ - (ANY, &[Activity::range(2017, 2017)]), - (BB, &[Activity::after(1990)]), - (MV, &[Activity::after(1990)]), - (SN, &[Activity::after(1990)]), - (ST, &[Activity::after(1990)]), - (TH, &[Activity::after(1990)]), - (HB, &[Activity::after(2018)]), - (HH, &[Activity::after(2018)]), - (NI, &[Activity::after(2018)]), - (SH, &[Activity::after(2018)]), - ]), - }, - // All Saints Day - Holiday { - name: "Allerheiligen".to_string(), - date: HDate::Fixed(November, 1), - states: GermanState::list(&[ - (BW, &[Activity::after(1990)]), - (BY, &[Activity::after(1990)]), - (NW, &[Activity::after(1990)]), - (RP, &[Activity::after(1990)]), - (SL, &[Activity::after(1990)]), - ]), - }, - // Repentance And Prayer Day - Holiday { - name: "Buß- und Bettag".to_string(), - date: HDate::Calculated(|year| { - // Der Buß- und Bettag ist immer ein Mittwoch, er liegt zwischen dem 16. und 22. November - let november_22 = Date::from_calendar_date(year, Month::November, 22) - .expect("22 Nov should exist every year"); - if november_22.weekday() == Weekday::Wednesday { - november_22 - } else { - november_22.prev_occurrence(Weekday::Wednesday) - } - }), - states: GermanState::list(&[ - (ANY, &[Activity::range(1990, 1994)]), - (SN, &[Activity::after(1990)]), - ]), - }, - // Christmas Day - Holiday { - name: "Erster Weihnachtstag".to_string(), - date: HDate::Fixed(December, 25), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - // Second Day Of Christmas - Holiday { - name: "Zweiter Weihnachtstag".to_string(), - date: HDate::Fixed(December, 26), - states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), - }, - ], - ) + Self(vec![ + // New Years Day + Holiday { + name: "Neujahrstag".to_string(), + date: HDate::Fixed(January, 1), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Epiphany + Holiday { + name: "Heilige drei Könige".to_string(), + date: HDate::Fixed(January, 6), + states: GermanState::list(&[ + (BW, &[Activity::after(1990)]), + (BY, &[Activity::after(1990)]), + (ST, &[Activity::after(1990)]), + ]), + }, + // International Women's Day + Holiday { + name: "Internationaler Frauentag".to_string(), + date: HDate::Fixed(March, 8), + states: GermanState::list(&[ + (BE, &[Activity::after(2019)]), + (MV, &[Activity::after(2023)]), + ]), + }, + // Good Friday + Holiday { + name: "Karfreitag".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Easter Monday + Holiday { + name: "Ostermontag".to_string(), + date: HDate::Calculated(utils::easter_monday), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Labour Day + Holiday { + name: "Tag der Arbeit".to_string(), + date: HDate::Fixed(May, 1), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Ascension Day + Holiday { + name: "Christi Himmelfahrt".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Whit Monday + Holiday { + name: "Pfingstmontag".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Corpus Christi + Holiday { + name: "Fronleichnam".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(59)), + states: GermanState::list(&[ + (BW, &[Activity::after(1990)]), + (BY, &[Activity::after(1990)]), + (HE, &[Activity::after(1990)]), + (NW, &[Activity::after(1990)]), + (RP, &[Activity::after(1990)]), + (SL, &[Activity::after(1990)]), + ]), + }, + // Assumption Day + Holiday { + name: "Mariä Himmelfahrt".to_string(), + date: HDate::Fixed(August, 15), + states: GermanState::list(&[ + (BY, &[Activity::after(1990)]), + (SL, &[Activity::after(1990)]), + ]), + }, + // World Children's Day + Holiday { + name: "Weltkindertag".to_string(), + date: HDate::Fixed(September, 20), + states: GermanState::list(&[(TH, &[Activity::after(2019)])]), + }, + // German Unity Day + Holiday { + name: "Tag der Deutschen Einheit".to_string(), + date: HDate::Fixed(October, 3), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Reformation Day + Holiday { + name: "Reformationstag".to_string(), + date: HDate::Fixed(October, 31), + states: GermanState::list(&[ + (ANY, &[Activity::range(2017, 2017)]), + (BB, &[Activity::after(1990)]), + (MV, &[Activity::after(1990)]), + (SN, &[Activity::after(1990)]), + (ST, &[Activity::after(1990)]), + (TH, &[Activity::after(1990)]), + (HB, &[Activity::after(2018)]), + (HH, &[Activity::after(2018)]), + (NI, &[Activity::after(2018)]), + (SH, &[Activity::after(2018)]), + ]), + }, + // All Saints Day + Holiday { + name: "Allerheiligen".to_string(), + date: HDate::Fixed(November, 1), + states: GermanState::list(&[ + (BW, &[Activity::after(1990)]), + (BY, &[Activity::after(1990)]), + (NW, &[Activity::after(1990)]), + (RP, &[Activity::after(1990)]), + (SL, &[Activity::after(1990)]), + ]), + }, + // Repentance And Prayer Day + Holiday { + name: "Buß- und Bettag".to_string(), + date: HDate::Calculated(|year| { + // Der Buß- und Bettag ist immer ein Mittwoch, er liegt zwischen dem 16. und 22. November + let november_22 = Date::from_calendar_date(year, Month::November, 22) + .expect("22 Nov should exist every year"); + + if november_22.weekday() == Weekday::Wednesday { + november_22 + } else { + november_22.prev_occurrence(Weekday::Wednesday) + } + }), + states: GermanState::list(&[ + (ANY, &[Activity::range(1990, 1994)]), + (SN, &[Activity::after(1990)]), + ]), + }, + // Christmas Day + Holiday { + name: "Erster Weihnachtstag".to_string(), + date: HDate::Fixed(December, 25), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + // Second Day Of Christmas + Holiday { + name: "Zweiter Weihnachtstag".to_string(), + date: HDate::Fixed(December, 26), + states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), + }, + ]) } } @@ -219,12 +217,12 @@ mod tests { use crate::{ HolidayChecker, - countries::{CountryCode, de::GermanState}, + countries::{CountryState, de::GermanState}, }; #[test] fn test_number_of_holidays() { - let checker = HolidayChecker::new(vec![CountryCode::DE]); + let checker = HolidayChecker::new(); let tests = [ (GermanState::BW, 12), @@ -246,7 +244,7 @@ mod tests { ]; for (state, num) in tests { assert_eq!( - checker.number_of_holidays(CountryCode::DE, &state.to_string(), 2025), + checker.number_of_holidays(CountryState::DE(state), 2025), num ); } @@ -254,27 +252,25 @@ mod tests { #[test] fn test_holidays() { - let checker = HolidayChecker::new(vec![CountryCode::DE]); - let country = CountryCode::DE; - let state = GermanState::ANY.to_string(); + let checker = HolidayChecker::new(); + let country = CountryState::DE(GermanState::ANY); // New Years Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 01 - 01)) + .is_holiday(country, 2025, date!(2025 - 01 - 01)) .is_some() ); // Epiphany assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 01 - 06)) + .is_holiday(country, 2025, date!(2025 - 01 - 06)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::BW.to_string(), + CountryState::DE(GermanState::BW), 2025, date!(2025 - 01 - 06) ) @@ -283,14 +279,13 @@ mod tests { // International Women's Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 03 - 08)) + .is_holiday(country, 2025, date!(2025 - 03 - 08)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::BE.to_string(), + CountryState::DE(GermanState::BE), 2025, date!(2025 - 03 - 08) ) @@ -299,44 +294,43 @@ mod tests { // Good Friday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 04 - 18)) + .is_holiday(country, 2025, date!(2025 - 04 - 18)) .is_some() ); // Easter Monday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 04 - 21)) + .is_holiday(country, 2025, date!(2025 - 04 - 21)) .is_some() ); // Labour Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 05 - 01)) + .is_holiday(country, 2025, date!(2025 - 05 - 01)) .is_some() ); // Ascension Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 05 - 29)) + .is_holiday(country, 2025, date!(2025 - 05 - 29)) .is_some() ); // Whit Monday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 06 - 09)) + .is_holiday(country, 2025, date!(2025 - 06 - 09)) .is_some() ); // Corpus Christi assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 06 - 19)) + .is_holiday(country, 2025, date!(2025 - 06 - 19)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::BW.to_string(), + CountryState::DE(GermanState::BW), 2025, date!(2025 - 06 - 19) ) @@ -345,14 +339,13 @@ mod tests { // Assumption Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 08 - 15)) + .is_holiday(country, 2025, date!(2025 - 08 - 15)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::BY.to_string(), + CountryState::DE(GermanState::BY), 2025, date!(2025 - 08 - 15) ) @@ -361,14 +354,13 @@ mod tests { // World Children's Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 09 - 20)) + .is_holiday(country, 2025, date!(2025 - 09 - 20)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::TH.to_string(), + CountryState::DE(GermanState::TH), 2025, date!(2025 - 09 - 20) ) @@ -377,20 +369,19 @@ mod tests { // German Unity Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 10 - 03)) + .is_holiday(country, 2025, date!(2025 - 10 - 03)) .is_some() ); // Reformation Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 10 - 31)) + .is_holiday(country, 2025, date!(2025 - 10 - 31)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::HH.to_string(), + CountryState::DE(GermanState::HH), 2025, date!(2025 - 10 - 31) ) @@ -399,14 +390,13 @@ mod tests { // All Saints Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 11 - 01)) + .is_holiday(country, 2025, date!(2025 - 11 - 01)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::BW.to_string(), + CountryState::DE(GermanState::BW), 2025, date!(2025 - 11 - 01) ) @@ -415,14 +405,13 @@ mod tests { // Repentance And Prayer Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 11 - 19)) + .is_holiday(country, 2025, date!(2025 - 11 - 19)) .is_none() ); assert!( checker .is_holiday( - country, - &GermanState::SN.to_string(), + CountryState::DE(GermanState::SN), 2025, date!(2025 - 11 - 19) ) @@ -431,13 +420,13 @@ mod tests { // Christmas Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 12 - 25)) + .is_holiday(country, 2025, date!(2025 - 12 - 25)) .is_some() ); // Second Day Of Christmas assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 12 - 26)) + .is_holiday(country, 2025, date!(2025 - 12 - 26)) .is_some() ); } diff --git a/src/countries/fr.rs b/src/countries/fr.rs index da68d0c..b662eaf 100644 --- a/src/countries/fr.rs +++ b/src/countries/fr.rs @@ -1,14 +1,14 @@ use time::Duration; use crate::{ - countries::{CountryHolidays, StateList}, + countries::StateList, holiday::{Activity, HDate, Holiday}, utils, }; -pub(super) struct FrenchHolidays; +pub(crate) struct FrenchHolidays(pub Vec>); -#[derive(strum::Display, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, strum::Display, Hash, PartialEq, Eq)] pub enum FrenchState { /// Bas-Rhin BasRhin, @@ -26,101 +26,99 @@ impl super::StateList for FrenchState { } } -impl CountryHolidays for FrenchHolidays { - fn new() -> (String, Vec) { +impl FrenchHolidays { + pub(crate) fn new() -> Self { use FrenchState::*; use time::Month::*; - ( - FrenchState::all_states_identifier(), - vec![ - // New Years Day - Holiday { - name: "Jour de l'an".to_string(), - date: HDate::Fixed(January, 1), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Good Friday - Holiday { - name: "Vendredi saint".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)), - states: FrenchState::list(&[ - (BasRhin, &[Activity::unlimited()]), - (HautRhin, &[Activity::unlimited()]), - (Moselle, &[Activity::unlimited()]), - ]), - }, - // Easter Monday - Holiday { - name: "Lundi de Pâques".to_string(), - date: HDate::Calculated(utils::easter_monday), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Labour Day - Holiday { - name: "Fête du travail".to_string(), - date: HDate::Fixed(May, 1), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Victory Day (Second World War) - Holiday { - name: "Victoire 1945".to_string(), - date: HDate::Fixed(May, 8), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Ascension Day - Holiday { - name: "Ascension".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Whit Monday - Holiday { - name: "Lundi de Pentecôte".to_string(), - date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // National Day (14th July) - Holiday { - name: "Fête nationale".to_string(), - date: HDate::Fixed(July, 14), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Assumption Day - Holiday { - name: "Assomption".to_string(), - date: HDate::Fixed(August, 15), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // All Saints Day - Holiday { - name: "Toussaint".to_string(), - date: HDate::Fixed(November, 1), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Victory Day (First World War) - Holiday { - name: "Armistice 1918".to_string(), - date: HDate::Fixed(November, 11), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Christmas Day - Holiday { - name: "Jour de Noël".to_string(), - date: HDate::Fixed(December, 25), - states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Second Day Of Christmas - Holiday { - name: "Saint Étienne".to_string(), - date: HDate::Fixed(December, 26), - states: FrenchState::list(&[ - (BasRhin, &[Activity::unlimited()]), - (HautRhin, &[Activity::unlimited()]), - (Moselle, &[Activity::unlimited()]), - ]), - }, - ], - ) + + Self(vec![ + // New Years Day + Holiday { + name: "Jour de l'an".to_string(), + date: HDate::Fixed(January, 1), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Good Friday + Holiday { + name: "Vendredi saint".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)), + states: FrenchState::list(&[ + (BasRhin, &[Activity::unlimited()]), + (HautRhin, &[Activity::unlimited()]), + (Moselle, &[Activity::unlimited()]), + ]), + }, + // Easter Monday + Holiday { + name: "Lundi de Pâques".to_string(), + date: HDate::Calculated(utils::easter_monday), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Labour Day + Holiday { + name: "Fête du travail".to_string(), + date: HDate::Fixed(May, 1), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Victory Day (Second World War) + Holiday { + name: "Victoire 1945".to_string(), + date: HDate::Fixed(May, 8), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Ascension Day + Holiday { + name: "Ascension".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Whit Monday + Holiday { + name: "Lundi de Pentecôte".to_string(), + date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // National Day (14th July) + Holiday { + name: "Fête nationale".to_string(), + date: HDate::Fixed(July, 14), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Assumption Day + Holiday { + name: "Assomption".to_string(), + date: HDate::Fixed(August, 15), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // All Saints Day + Holiday { + name: "Toussaint".to_string(), + date: HDate::Fixed(November, 1), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Victory Day (First World War) + Holiday { + name: "Armistice 1918".to_string(), + date: HDate::Fixed(November, 11), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Christmas Day + Holiday { + name: "Jour de Noël".to_string(), + date: HDate::Fixed(December, 25), + states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Second Day Of Christmas + Holiday { + name: "Saint Étienne".to_string(), + date: HDate::Fixed(December, 26), + states: FrenchState::list(&[ + (BasRhin, &[Activity::unlimited()]), + (HautRhin, &[Activity::unlimited()]), + (Moselle, &[Activity::unlimited()]), + ]), + }, + ]) } } @@ -130,12 +128,12 @@ mod tests { use crate::{ HolidayChecker, - countries::{CountryCode, fr::FrenchState}, + countries::{CountryState, fr::FrenchState}, }; #[test] fn test_number_of_holidays() { - let checker = HolidayChecker::new(vec![CountryCode::FR]); + let checker = HolidayChecker::new(); let tests = [ (FrenchState::BasRhin, 14), @@ -145,7 +143,7 @@ mod tests { ]; for (state, num) in tests { assert_eq!( - checker.number_of_holidays(CountryCode::FR, &state.to_string(), 2025), + checker.number_of_holidays(CountryState::FR(state), 2025), num ); } @@ -153,27 +151,25 @@ mod tests { #[test] fn test_holidays() { - let checker = HolidayChecker::new(vec![CountryCode::FR]); - let country = CountryCode::FR; - let state = FrenchState::ANY.to_string(); + let checker = HolidayChecker::new(); + let country = CountryState::FR(FrenchState::ANY); // New Years Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 01 - 01)) + .is_holiday(country, 2025, date!(2025 - 01 - 01)) .is_some() ); // Good Friday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 04 - 18)) + .is_holiday(country, 2025, date!(2025 - 04 - 18)) .is_none() ); assert!( checker .is_holiday( - country, - &FrenchState::BasRhin.to_string(), + CountryState::FR(FrenchState::BasRhin), 2025, date!(2025 - 04 - 18) ) @@ -182,74 +178,73 @@ mod tests { // Easter Monday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 04 - 21)) + .is_holiday(country, 2025, date!(2025 - 04 - 21)) .is_some() ); // Labour Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 05 - 01)) + .is_holiday(country, 2025, date!(2025 - 05 - 01)) .is_some() ); // Victory Day (Second World War) assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 05 - 08)) + .is_holiday(country, 2025, date!(2025 - 05 - 08)) .is_some() ); // Ascension Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 05 - 29)) + .is_holiday(country, 2025, date!(2025 - 05 - 29)) .is_some() ); // Whit Monday assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 06 - 09)) + .is_holiday(country, 2025, date!(2025 - 06 - 09)) .is_some() ); // National Day (14th July) assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 07 - 14)) + .is_holiday(country, 2025, date!(2025 - 07 - 14)) .is_some() ); // Assumption Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 08 - 15)) + .is_holiday(country, 2025, date!(2025 - 08 - 15)) .is_some() ); // All Saints Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 11 - 01)) + .is_holiday(country, 2025, date!(2025 - 11 - 01)) .is_some() ); // Victory Day (First World War) assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 11 - 11)) + .is_holiday(country, 2025, date!(2025 - 11 - 11)) .is_some() ); // Christmas Day assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 12 - 25)) + .is_holiday(country, 2025, date!(2025 - 12 - 25)) .is_some() ); // Second Day Of Christmas assert!( checker - .is_holiday(country, &state, 2025, date!(2025 - 12 - 26)) + .is_holiday(country, 2025, date!(2025 - 12 - 26)) .is_none() ); assert!( checker .is_holiday( - country, - &FrenchState::BasRhin.to_string(), + CountryState::FR(FrenchState::BasRhin), 2025, date!(2025 - 12 - 26) ) diff --git a/src/countries/mod.rs b/src/countries/mod.rs index 766cd12..c62cbb7 100644 --- a/src/countries/mod.rs +++ b/src/countries/mod.rs @@ -1,22 +1,18 @@ -mod de; -mod fr; -mod us; +pub mod de; +pub mod fr; +pub mod us; use std::{collections::HashMap, fmt::Display, hash::Hash}; use crate::{ - countries::{de::GermanHolidays, fr::FrenchHolidays, us::USHolidays}, + countries::{ + de::{GermanHolidays, GermanState}, + fr::{FrenchHolidays, FrenchState}, + us::{USHolidays, USState}, + }, holiday::{Activity, Holiday}, }; -trait CountryHolidays -where - T: StateList, -{ - /// Returns a Tuple consisting of the identifier for all states and the holiday days - fn new() -> (String, Vec); -} - /// Represents country codes for holiday calculations /// /// # Variants @@ -27,35 +23,25 @@ where /// # Purpose /// Provides a type-safe way to specify countries for holiday-related operations #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum CountryCode { +pub enum CountryState { /// Germany - DE, + DE(GermanState), /// The United States - US, + US(USState), /// France - FR, -} - -impl CountryCode { - pub(crate) fn get_holidays(&self) -> (String, Vec) { - match self { - CountryCode::DE => GermanHolidays::new(), - CountryCode::US => USHolidays::new(), - CountryCode::FR => FrenchHolidays::new(), - } - } + FR(FrenchState), } pub(crate) trait StateList where - Self: Sized + Display + Hash + Eq, + Self: Sized + Display + Hash + Eq + Clone + Copy, { - fn list(states: &[(Self, &[Activity])]) -> HashMap> { + fn list(states: &[(Self, &[Activity])]) -> HashMap> { let mut map = HashMap::new(); for state in states { - map.insert(state.0.to_string(), state.1.to_vec()); + map.insert(state.0, state.1.to_vec()); } map } diff --git a/src/countries/us.rs b/src/countries/us.rs index b62068b..d2db733 100644 --- a/src/countries/us.rs +++ b/src/countries/us.rs @@ -1,14 +1,14 @@ use time::{Date, Month, Weekday}; use crate::{ - countries::{CountryHolidays, StateList}, + countries::StateList, holiday::{Activity, HDate, Holiday}, utils, }; -pub(super) struct USHolidays; +pub(crate) struct USHolidays(pub Vec>); -#[derive(strum::Display, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, strum::Display, Hash, PartialEq, Eq)] pub enum USState { // Northeast Maine, @@ -82,93 +82,91 @@ impl super::StateList for USState { } } -impl CountryHolidays for USHolidays { - fn new() -> (String, Vec) { +impl USHolidays { + pub(crate) fn new() -> Self { use USState::*; use time::Month::*; - ( - USState::all_states_identifier(), - vec![ - // NATIONAL HOLIDAYS - // New Years Day - Holiday { - name: "New Year's Day".to_string(), - date: HDate::Fixed(January, 1), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Birthday of Martin Luther King, Jr. (Third Monday in January) - Holiday { - name: "Martin Luther King, Jr. Day".to_string(), - date: HDate::Calculated(|year| { - utils::nth_weekday_in_month(year, Month::January, Weekday::Monday, 3) - }), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Inauguration Day (January 20, every 4 years following a presidential election) - // Washington's Birthday (Also known as Presidents Day; third Monday in February) - Holiday { - name: "Washington's Birthday".to_string(), - date: HDate::Calculated(|year| { - utils::nth_weekday_in_month(year, Month::February, Weekday::Monday, 3) - }), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Memorial Day (Last Monday in May) - Holiday { - name: "Memorial Day".to_string(), - date: HDate::Calculated(|year| utils::last_monday_in_month(year, Month::May)), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Juneteenth National Independence Day (June 19) - Holiday { - name: "Juneteenth".to_string(), - date: HDate::Fixed(Month::June, 19), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Independence Day (July 4) - Holiday { - name: "Independence Day".to_string(), - date: HDate::Fixed(Month::July, 4), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Labor Day (First Monday in September) - Holiday { - name: "Labor Day".to_string(), - date: HDate::Calculated(|year| { - utils::nth_weekday_in_month(year, Month::September, Weekday::Monday, 1) - }), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Columbus Day (Second Monday in October) - Holiday { - name: "Columbus Day".to_string(), - date: HDate::Calculated(|year| { - utils::nth_weekday_in_month(year, Month::October, Weekday::Monday, 2) - }), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Veterans Day (November 11) - Holiday { - name: "Veterans Day".to_string(), - date: HDate::Fixed(Month::November, 11), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Thanksgiving Day (Fourth Thursday in November) - Holiday { - name: "Thanksgiving Day".to_string(), - date: HDate::Calculated(|year| { - utils::nth_weekday_in_month(year, Month::November, Weekday::Thursday, 4) - }), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - // Christmas Day (December 25) - Holiday { - name: "Christmas Day".to_string(), - date: HDate::Fixed(Month::December, 25), - states: USState::list(&[(ANY, &[Activity::unlimited()])]), - }, - ], - ) + Self(vec![ + // NATIONAL HOLIDAYS + + // New Years Day + Holiday { + name: "New Year's Day".to_string(), + date: HDate::Fixed(January, 1), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Birthday of Martin Luther King, Jr. (Third Monday in January) + Holiday { + name: "Martin Luther King, Jr. Day".to_string(), + date: HDate::Calculated(|year| { + utils::nth_weekday_in_month(year, Month::January, Weekday::Monday, 3) + }), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Inauguration Day (January 20, every 4 years following a presidential election) + // Washington's Birthday (Also known as Presidents Day; third Monday in February) + Holiday { + name: "Washington's Birthday".to_string(), + date: HDate::Calculated(|year| { + utils::nth_weekday_in_month(year, Month::February, Weekday::Monday, 3) + }), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Memorial Day (Last Monday in May) + Holiday { + name: "Memorial Day".to_string(), + date: HDate::Calculated(|year| utils::last_monday_in_month(year, Month::May)), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Juneteenth National Independence Day (June 19) + Holiday { + name: "Juneteenth".to_string(), + date: HDate::Fixed(Month::June, 19), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Independence Day (July 4) + Holiday { + name: "Independence Day".to_string(), + date: HDate::Fixed(Month::July, 4), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Labor Day (First Monday in September) + Holiday { + name: "Labor Day".to_string(), + date: HDate::Calculated(|year| { + utils::nth_weekday_in_month(year, Month::September, Weekday::Monday, 1) + }), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Columbus Day (Second Monday in October) + Holiday { + name: "Columbus Day".to_string(), + date: HDate::Calculated(|year| { + utils::nth_weekday_in_month(year, Month::October, Weekday::Monday, 2) + }), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Veterans Day (November 11) + Holiday { + name: "Veterans Day".to_string(), + date: HDate::Fixed(Month::November, 11), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Thanksgiving Day (Fourth Thursday in November) + Holiday { + name: "Thanksgiving Day".to_string(), + date: HDate::Calculated(|year| { + utils::nth_weekday_in_month(year, Month::November, Weekday::Thursday, 4) + }), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + // Christmas Day (December 25) + Holiday { + name: "Christmas Day".to_string(), + date: HDate::Fixed(Month::December, 25), + states: USState::list(&[(ANY, &[Activity::unlimited()])]), + }, + ]) } } diff --git a/src/holiday.rs b/src/holiday.rs index 9445f3e..2589262 100644 --- a/src/holiday.rs +++ b/src/holiday.rs @@ -1,7 +1,9 @@ -use std::collections::HashMap; +use std::{collections::HashMap, hash::Hash}; use time::{Date, Month}; +use crate::HolidayDate; + #[derive(Debug, Clone)] pub(crate) struct Activity { unlimited: bool, @@ -49,22 +51,49 @@ pub(crate) enum HDate { Calculated(fn(i32) -> Date), } -#[derive(Debug, Clone)] -pub(crate) struct Holiday { - pub(crate) name: String, - pub(crate) date: HDate, - pub(crate) states: HashMap>, +impl HDate { + pub(crate) fn to_date(&self, year: i32, name: &str) -> HolidayDate { + match self { + HDate::Calculated(calc_fn) => { + // For holidays with calculation function + HolidayDate { + date: calc_fn(year), + name: name.to_string(), + } + } + HDate::Fixed(month, day) => { + // For fixed date holidays + HolidayDate { + date: Date::from_calendar_date(year, *month, *day).unwrap(), + name: name.to_string(), + } + } + } + } } -impl Holiday { - pub(crate) fn is_active(&self, year: i32, all_states_identifier: &str, state: &str) -> bool { - if let Some(all_states) = self.states.get(all_states_identifier) { +#[derive(Debug, Clone)] +pub(crate) struct Holiday +where + T: Hash + Eq, +{ + pub(crate) name: String, + pub(crate) date: HDate, + pub(crate) states: HashMap>, +} + +impl Holiday +where + T: Hash + Eq, +{ + pub(crate) fn is_active(&self, year: i32, all_states_identifier: T, state: T) -> bool { + if let Some(all_states) = self.states.get(&all_states_identifier) { // if holiday is valid for all states and is active in that year if all_states.iter().any(|activity| activity.is_active(year)) { return true; } } - if let Some(all_states) = self.states.get(state) { + if let Some(all_states) = self.states.get(&state) { // if holiday is only valid for this states and is active in that year if all_states.iter().any(|activity| activity.is_active(year)) { return true; diff --git a/src/lib.rs b/src/lib.rs index 41a0dc5..d48f703 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,21 @@ -use std::collections::HashMap; - pub use time::Date; +use crate::countries::de::{GermanHolidays, GermanState}; +use crate::countries::fr::{FrenchHolidays, FrenchState}; +use crate::countries::us::{USHolidays, USState}; use crate::holiday::{HDate, Holiday}; mod countries; mod holiday; pub(crate) mod utils; -pub use countries::CountryCode; +use countries::CountryState; /// A struct to manage and check holidays for different countries pub struct HolidayChecker { - countries: HashMap)>, + de: GermanHolidays, + us: USHolidays, + fr: FrenchHolidays, } impl HolidayChecker { @@ -23,15 +26,12 @@ impl HolidayChecker { /// /// # Returns /// A new HolidayChecker instance with holidays for the specified countries - pub fn new(countries: Vec) -> Self { - let mut map = HashMap::new(); - - for country in countries { - let holidays = country.get_holidays(); - map.insert(country, holidays); + pub fn new() -> Self { + Self { + de: GermanHolidays::new(), + fr: FrenchHolidays::new(), + us: USHolidays::new(), } - - Self { countries: map } } /// Retrieves a list of holidays for a specific country, state, and year @@ -43,14 +43,8 @@ impl HolidayChecker { /// /// # Returns /// A vector of HolidayDate for the specified country, state, and year - pub fn holiday_list(&self, country: CountryCode, state: &str, year: i32) -> Vec { - Self::get_years_holidays( - self.countries - .get(&country) - .unwrap_or(&(String::new(), vec![])), - year, - state, - ) + pub fn holiday_list(&self, country: CountryState, year: i32) -> Vec { + self.get_years_holidays(country, year) } /// Counts the number of holidays for a specific country, state, and year @@ -62,15 +56,8 @@ impl HolidayChecker { /// /// # Returns /// The number of holidays for the specified country, state, and year - pub fn number_of_holidays(&self, country: CountryCode, state: &str, year: i32) -> usize { - Self::get_years_holidays( - self.countries - .get(&country) - .unwrap_or(&(String::new(), vec![])), - year, - state, - ) - .len() + pub fn number_of_holidays(&self, country: CountryState, year: i32) -> usize { + self.get_years_holidays(country, year).len() } /// Checks if a specific date is a holiday for a given country, state, and year @@ -83,26 +70,14 @@ impl HolidayChecker { /// /// # Returns /// An Option containing the HolidayDate if the date is a holiday, None otherwise - pub fn is_holiday( - &self, - country: CountryCode, - state: &str, - year: i32, - date: Date, - ) -> Option { - Self::get_years_holidays( - self.countries - .get(&country) - .unwrap_or(&(String::new(), vec![])), - year, - state, - ) - .iter() - .find(|holiday| holiday.date == date) - .map(|holiday| HolidayDate { - name: holiday.name.clone(), - date, - }) + pub fn is_holiday(&self, country: CountryState, year: i32, date: Date) -> Option { + self.get_years_holidays(country, year) + .iter() + .find(|holiday| holiday.date == date) + .map(|holiday| HolidayDate { + name: holiday.name.clone(), + date, + }) } /// Internal method to retrieve holidays for a specific year and state @@ -114,37 +89,71 @@ impl HolidayChecker { /// /// # Returns /// A vector of HolidayDate for the specified year and state - fn get_years_holidays( - holidays: &(String, Vec), - year: i32, - state: &str, - ) -> Vec { - let (all_states_identifier, holidays) = holidays; - holidays - .iter() - .filter_map(|holiday| { - // check if holiday is active - if !holiday.is_active(year, all_states_identifier, state) { - return None; - } - match holiday.date { - HDate::Calculated(calc_fn) => { - // For holidays with calculation function - Some(HolidayDate { - date: calc_fn(year), - name: holiday.name.clone(), - }) + fn get_years_holidays(&self, country: CountryState, year: i32) -> Vec { + match country { + CountryState::DE(state) => self + .de + .0 + .iter() + .filter_map(|holiday| { + // check if holiday is active + if !holiday.is_active(year, GermanState::ANY, state) { + return None; } - HDate::Fixed(month, day) => { - // For fixed date holidays - Some(HolidayDate { - date: Date::from_calendar_date(year, month, day).unwrap(), - name: holiday.name.clone(), - }) + Some(holiday.date.to_date(year, &holiday.name)) + }) + .collect(), + CountryState::US(state) => self + .us + .0 + .iter() + .filter_map(|holiday| { + // check if holiday is active + if !holiday.is_active(year, USState::ANY, state) { + return None; } - } - }) - .collect() + Some(holiday.date.to_date(year, &holiday.name)) + }) + .collect(), + CountryState::FR(state) => self + .fr + .0 + .iter() + .filter_map(|holiday| { + // check if holiday is active + if !holiday.is_active(year, FrenchState::ANY, state) { + return None; + } + Some(holiday.date.to_date(year, &holiday.name)) + }) + .collect(), + } + + // holidays + // .iter() + // .filter_map(|holiday| { + // // check if holiday is active + // if !holiday.is_active(year, all_states_identifier, state) { + // return None; + // } + // match holiday.date { + // HDate::Calculated(calc_fn) => { + // // For holidays with calculation function + // Some(HolidayDate { + // date: calc_fn(year), + // name: holiday.name.clone(), + // }) + // } + // HDate::Fixed(month, day) => { + // // For fixed date holidays + // Some(HolidayDate { + // date: Date::from_calendar_date(year, month, day).unwrap(), + // name: holiday.name.clone(), + // }) + // } + // } + // }) + // .collect() } }