api changes

This commit is contained in:
Hlars 2025-08-05 22:39:46 +02:00
parent d82ef99c55
commit 8a78e8f901
7 changed files with 536 additions and 521 deletions

View File

@ -4,6 +4,15 @@ version = "0.1.1"
edition = "2024" edition = "2024"
publish = ["kellnr"] publish = ["kellnr"]
# [features]
# default = ["de"]
# # Includes Germany
# de = []
# # Includes France
# fr = []
# # Includes The United States
# us = []
[dependencies] [dependencies]
time = "0.3.41" time = "0.3.41"
strum = { version = "0.27.2", features = ["derive"] } strum = { version = "0.27.2", features = ["derive"] }

View File

@ -1,14 +1,14 @@
use time::{Date, Duration, Month, Weekday}; use time::{Date, Duration, Month, Weekday};
use crate::{ use crate::{
countries::{CountryHolidays, StateList}, countries::StateList,
holiday::{Activity, HDate, Holiday}, holiday::{Activity, HDate, Holiday},
utils::{self}, utils::{self},
}; };
pub(super) struct GermanHolidays; pub(crate) struct GermanHolidays(pub Vec<Holiday<GermanState>>);
#[derive(strum::Display, Hash, PartialEq, Eq)] #[derive(Debug, strum::Display, Clone, Copy, Hash, PartialEq, Eq)]
pub enum GermanState { pub enum GermanState {
/// Baden-Württemberg /// Baden-Württemberg
BW, BW,
@ -52,164 +52,162 @@ impl super::StateList for GermanState {
} }
} }
impl CountryHolidays<GermanState> for GermanHolidays { impl GermanHolidays {
fn new() -> (String, Vec<Holiday>) { pub(crate) fn new() -> Self {
use GermanState::*; use GermanState::*;
use time::Month::*; 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 { Self(vec![
november_22 // New Years Day
} else { Holiday {
november_22.prev_occurrence(Weekday::Wednesday) name: "Neujahrstag".to_string(),
} date: HDate::Fixed(January, 1),
}), states: GermanState::list(&[(ANY, &[Activity::after(1990)])]),
states: GermanState::list(&[ },
(ANY, &[Activity::range(1990, 1994)]), // Epiphany
(SN, &[Activity::after(1990)]), Holiday {
]), name: "Heilige drei Könige".to_string(),
}, date: HDate::Fixed(January, 6),
// Christmas Day states: GermanState::list(&[
Holiday { (BW, &[Activity::after(1990)]),
name: "Erster Weihnachtstag".to_string(), (BY, &[Activity::after(1990)]),
date: HDate::Fixed(December, 25), (ST, &[Activity::after(1990)]),
states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), ]),
}, },
// Second Day Of Christmas // International Women's Day
Holiday { Holiday {
name: "Zweiter Weihnachtstag".to_string(), name: "Internationaler Frauentag".to_string(),
date: HDate::Fixed(December, 26), date: HDate::Fixed(March, 8),
states: GermanState::list(&[(ANY, &[Activity::after(1990)])]), 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::{ use crate::{
HolidayChecker, HolidayChecker,
countries::{CountryCode, de::GermanState}, countries::{CountryState, de::GermanState},
}; };
#[test] #[test]
fn test_number_of_holidays() { fn test_number_of_holidays() {
let checker = HolidayChecker::new(vec![CountryCode::DE]); let checker = HolidayChecker::new();
let tests = [ let tests = [
(GermanState::BW, 12), (GermanState::BW, 12),
@ -246,7 +244,7 @@ mod tests {
]; ];
for (state, num) in tests { for (state, num) in tests {
assert_eq!( assert_eq!(
checker.number_of_holidays(CountryCode::DE, &state.to_string(), 2025), checker.number_of_holidays(CountryState::DE(state), 2025),
num num
); );
} }
@ -254,27 +252,25 @@ mod tests {
#[test] #[test]
fn test_holidays() { fn test_holidays() {
let checker = HolidayChecker::new(vec![CountryCode::DE]); let checker = HolidayChecker::new();
let country = CountryCode::DE; let country = CountryState::DE(GermanState::ANY);
let state = GermanState::ANY.to_string();
// New Years Day // New Years Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 01 - 01)) .is_holiday(country, 2025, date!(2025 - 01 - 01))
.is_some() .is_some()
); );
// Epiphany // Epiphany
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 01 - 06)) .is_holiday(country, 2025, date!(2025 - 01 - 06))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::BW),
&GermanState::BW.to_string(),
2025, 2025,
date!(2025 - 01 - 06) date!(2025 - 01 - 06)
) )
@ -283,14 +279,13 @@ mod tests {
// International Women's Day // International Women's Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 03 - 08)) .is_holiday(country, 2025, date!(2025 - 03 - 08))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::BE),
&GermanState::BE.to_string(),
2025, 2025,
date!(2025 - 03 - 08) date!(2025 - 03 - 08)
) )
@ -299,44 +294,43 @@ mod tests {
// Good Friday // Good Friday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 04 - 18)) .is_holiday(country, 2025, date!(2025 - 04 - 18))
.is_some() .is_some()
); );
// Easter Monday // Easter Monday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 04 - 21)) .is_holiday(country, 2025, date!(2025 - 04 - 21))
.is_some() .is_some()
); );
// Labour Day // Labour Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 05 - 01)) .is_holiday(country, 2025, date!(2025 - 05 - 01))
.is_some() .is_some()
); );
// Ascension Day // Ascension Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 05 - 29)) .is_holiday(country, 2025, date!(2025 - 05 - 29))
.is_some() .is_some()
); );
// Whit Monday // Whit Monday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 06 - 09)) .is_holiday(country, 2025, date!(2025 - 06 - 09))
.is_some() .is_some()
); );
// Corpus Christi // Corpus Christi
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 06 - 19)) .is_holiday(country, 2025, date!(2025 - 06 - 19))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::BW),
&GermanState::BW.to_string(),
2025, 2025,
date!(2025 - 06 - 19) date!(2025 - 06 - 19)
) )
@ -345,14 +339,13 @@ mod tests {
// Assumption Day // Assumption Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 08 - 15)) .is_holiday(country, 2025, date!(2025 - 08 - 15))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::BY),
&GermanState::BY.to_string(),
2025, 2025,
date!(2025 - 08 - 15) date!(2025 - 08 - 15)
) )
@ -361,14 +354,13 @@ mod tests {
// World Children's Day // World Children's Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 09 - 20)) .is_holiday(country, 2025, date!(2025 - 09 - 20))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::TH),
&GermanState::TH.to_string(),
2025, 2025,
date!(2025 - 09 - 20) date!(2025 - 09 - 20)
) )
@ -377,20 +369,19 @@ mod tests {
// German Unity Day // German Unity Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 10 - 03)) .is_holiday(country, 2025, date!(2025 - 10 - 03))
.is_some() .is_some()
); );
// Reformation Day // Reformation Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 10 - 31)) .is_holiday(country, 2025, date!(2025 - 10 - 31))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::HH),
&GermanState::HH.to_string(),
2025, 2025,
date!(2025 - 10 - 31) date!(2025 - 10 - 31)
) )
@ -399,14 +390,13 @@ mod tests {
// All Saints Day // All Saints Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 11 - 01)) .is_holiday(country, 2025, date!(2025 - 11 - 01))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::BW),
&GermanState::BW.to_string(),
2025, 2025,
date!(2025 - 11 - 01) date!(2025 - 11 - 01)
) )
@ -415,14 +405,13 @@ mod tests {
// Repentance And Prayer Day // Repentance And Prayer Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 11 - 19)) .is_holiday(country, 2025, date!(2025 - 11 - 19))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::DE(GermanState::SN),
&GermanState::SN.to_string(),
2025, 2025,
date!(2025 - 11 - 19) date!(2025 - 11 - 19)
) )
@ -431,13 +420,13 @@ mod tests {
// Christmas Day // Christmas Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 12 - 25)) .is_holiday(country, 2025, date!(2025 - 12 - 25))
.is_some() .is_some()
); );
// Second Day Of Christmas // Second Day Of Christmas
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 12 - 26)) .is_holiday(country, 2025, date!(2025 - 12 - 26))
.is_some() .is_some()
); );
} }

View File

@ -1,14 +1,14 @@
use time::Duration; use time::Duration;
use crate::{ use crate::{
countries::{CountryHolidays, StateList}, countries::StateList,
holiday::{Activity, HDate, Holiday}, holiday::{Activity, HDate, Holiday},
utils, utils,
}; };
pub(super) struct FrenchHolidays; pub(crate) struct FrenchHolidays(pub Vec<Holiday<FrenchState>>);
#[derive(strum::Display, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, strum::Display, Hash, PartialEq, Eq)]
pub enum FrenchState { pub enum FrenchState {
/// Bas-Rhin /// Bas-Rhin
BasRhin, BasRhin,
@ -26,101 +26,99 @@ impl super::StateList for FrenchState {
} }
} }
impl CountryHolidays<FrenchState> for FrenchHolidays { impl FrenchHolidays {
fn new() -> (String, Vec<Holiday>) { pub(crate) fn new() -> Self {
use FrenchState::*; use FrenchState::*;
use time::Month::*; use time::Month::*;
(
FrenchState::all_states_identifier(), Self(vec![
vec![ // New Years Day
// New Years Day Holiday {
Holiday { name: "Jour de l'an".to_string(),
name: "Jour de l'an".to_string(), date: HDate::Fixed(January, 1),
date: HDate::Fixed(January, 1), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Good Friday
// Good Friday Holiday {
Holiday { name: "Vendredi saint".to_string(),
name: "Vendredi saint".to_string(), date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)),
date: HDate::Calculated(|year| utils::easter_monday(year) - Duration::days(3)), states: FrenchState::list(&[
states: FrenchState::list(&[ (BasRhin, &[Activity::unlimited()]),
(BasRhin, &[Activity::unlimited()]), (HautRhin, &[Activity::unlimited()]),
(HautRhin, &[Activity::unlimited()]), (Moselle, &[Activity::unlimited()]),
(Moselle, &[Activity::unlimited()]), ]),
]), },
}, // Easter Monday
// Easter Monday Holiday {
Holiday { name: "Lundi de Pâques".to_string(),
name: "Lundi de Pâques".to_string(), date: HDate::Calculated(utils::easter_monday),
date: HDate::Calculated(utils::easter_monday), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Labour Day
// Labour Day Holiday {
Holiday { name: "Fête du travail".to_string(),
name: "Fête du travail".to_string(), date: HDate::Fixed(May, 1),
date: HDate::Fixed(May, 1), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Victory Day (Second World War)
// Victory Day (Second World War) Holiday {
Holiday { name: "Victoire 1945".to_string(),
name: "Victoire 1945".to_string(), date: HDate::Fixed(May, 8),
date: HDate::Fixed(May, 8), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Ascension Day
// Ascension Day Holiday {
Holiday { name: "Ascension".to_string(),
name: "Ascension".to_string(), date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)),
date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(38)), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Whit Monday
// Whit Monday Holiday {
Holiday { name: "Lundi de Pentecôte".to_string(),
name: "Lundi de Pentecôte".to_string(), date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)),
date: HDate::Calculated(|year| utils::easter_monday(year) + Duration::days(49)), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // National Day (14th July)
// National Day (14th July) Holiday {
Holiday { name: "Fête nationale".to_string(),
name: "Fête nationale".to_string(), date: HDate::Fixed(July, 14),
date: HDate::Fixed(July, 14), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Assumption Day
// Assumption Day Holiday {
Holiday { name: "Assomption".to_string(),
name: "Assomption".to_string(), date: HDate::Fixed(August, 15),
date: HDate::Fixed(August, 15), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // All Saints Day
// All Saints Day Holiday {
Holiday { name: "Toussaint".to_string(),
name: "Toussaint".to_string(), date: HDate::Fixed(November, 1),
date: HDate::Fixed(November, 1), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Victory Day (First World War)
// Victory Day (First World War) Holiday {
Holiday { name: "Armistice 1918".to_string(),
name: "Armistice 1918".to_string(), date: HDate::Fixed(November, 11),
date: HDate::Fixed(November, 11), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Christmas Day
// Christmas Day Holiday {
Holiday { name: "Jour de Noël".to_string(),
name: "Jour de Noël".to_string(), date: HDate::Fixed(December, 25),
date: HDate::Fixed(December, 25), states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]),
states: FrenchState::list(&[(ANY, &[Activity::unlimited()])]), },
}, // Second Day Of Christmas
// Second Day Of Christmas Holiday {
Holiday { name: "Saint Étienne".to_string(),
name: "Saint Étienne".to_string(), date: HDate::Fixed(December, 26),
date: HDate::Fixed(December, 26), states: FrenchState::list(&[
states: FrenchState::list(&[ (BasRhin, &[Activity::unlimited()]),
(BasRhin, &[Activity::unlimited()]), (HautRhin, &[Activity::unlimited()]),
(HautRhin, &[Activity::unlimited()]), (Moselle, &[Activity::unlimited()]),
(Moselle, &[Activity::unlimited()]), ]),
]), },
}, ])
],
)
} }
} }
@ -130,12 +128,12 @@ mod tests {
use crate::{ use crate::{
HolidayChecker, HolidayChecker,
countries::{CountryCode, fr::FrenchState}, countries::{CountryState, fr::FrenchState},
}; };
#[test] #[test]
fn test_number_of_holidays() { fn test_number_of_holidays() {
let checker = HolidayChecker::new(vec![CountryCode::FR]); let checker = HolidayChecker::new();
let tests = [ let tests = [
(FrenchState::BasRhin, 14), (FrenchState::BasRhin, 14),
@ -145,7 +143,7 @@ mod tests {
]; ];
for (state, num) in tests { for (state, num) in tests {
assert_eq!( assert_eq!(
checker.number_of_holidays(CountryCode::FR, &state.to_string(), 2025), checker.number_of_holidays(CountryState::FR(state), 2025),
num num
); );
} }
@ -153,27 +151,25 @@ mod tests {
#[test] #[test]
fn test_holidays() { fn test_holidays() {
let checker = HolidayChecker::new(vec![CountryCode::FR]); let checker = HolidayChecker::new();
let country = CountryCode::FR; let country = CountryState::FR(FrenchState::ANY);
let state = FrenchState::ANY.to_string();
// New Years Day // New Years Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 01 - 01)) .is_holiday(country, 2025, date!(2025 - 01 - 01))
.is_some() .is_some()
); );
// Good Friday // Good Friday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 04 - 18)) .is_holiday(country, 2025, date!(2025 - 04 - 18))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::FR(FrenchState::BasRhin),
&FrenchState::BasRhin.to_string(),
2025, 2025,
date!(2025 - 04 - 18) date!(2025 - 04 - 18)
) )
@ -182,74 +178,73 @@ mod tests {
// Easter Monday // Easter Monday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 04 - 21)) .is_holiday(country, 2025, date!(2025 - 04 - 21))
.is_some() .is_some()
); );
// Labour Day // Labour Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 05 - 01)) .is_holiday(country, 2025, date!(2025 - 05 - 01))
.is_some() .is_some()
); );
// Victory Day (Second World War) // Victory Day (Second World War)
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 05 - 08)) .is_holiday(country, 2025, date!(2025 - 05 - 08))
.is_some() .is_some()
); );
// Ascension Day // Ascension Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 05 - 29)) .is_holiday(country, 2025, date!(2025 - 05 - 29))
.is_some() .is_some()
); );
// Whit Monday // Whit Monday
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 06 - 09)) .is_holiday(country, 2025, date!(2025 - 06 - 09))
.is_some() .is_some()
); );
// National Day (14th July) // National Day (14th July)
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 07 - 14)) .is_holiday(country, 2025, date!(2025 - 07 - 14))
.is_some() .is_some()
); );
// Assumption Day // Assumption Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 08 - 15)) .is_holiday(country, 2025, date!(2025 - 08 - 15))
.is_some() .is_some()
); );
// All Saints Day // All Saints Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 11 - 01)) .is_holiday(country, 2025, date!(2025 - 11 - 01))
.is_some() .is_some()
); );
// Victory Day (First World War) // Victory Day (First World War)
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 11 - 11)) .is_holiday(country, 2025, date!(2025 - 11 - 11))
.is_some() .is_some()
); );
// Christmas Day // Christmas Day
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 12 - 25)) .is_holiday(country, 2025, date!(2025 - 12 - 25))
.is_some() .is_some()
); );
// Second Day Of Christmas // Second Day Of Christmas
assert!( assert!(
checker checker
.is_holiday(country, &state, 2025, date!(2025 - 12 - 26)) .is_holiday(country, 2025, date!(2025 - 12 - 26))
.is_none() .is_none()
); );
assert!( assert!(
checker checker
.is_holiday( .is_holiday(
country, CountryState::FR(FrenchState::BasRhin),
&FrenchState::BasRhin.to_string(),
2025, 2025,
date!(2025 - 12 - 26) date!(2025 - 12 - 26)
) )

View File

@ -1,22 +1,18 @@
mod de; pub mod de;
mod fr; pub mod fr;
mod us; pub mod us;
use std::{collections::HashMap, fmt::Display, hash::Hash}; use std::{collections::HashMap, fmt::Display, hash::Hash};
use crate::{ use crate::{
countries::{de::GermanHolidays, fr::FrenchHolidays, us::USHolidays}, countries::{
de::{GermanHolidays, GermanState},
fr::{FrenchHolidays, FrenchState},
us::{USHolidays, USState},
},
holiday::{Activity, Holiday}, holiday::{Activity, Holiday},
}; };
trait CountryHolidays<T>
where
T: StateList,
{
/// Returns a Tuple consisting of the identifier for all states and the holiday days
fn new() -> (String, Vec<Holiday>);
}
/// Represents country codes for holiday calculations /// Represents country codes for holiday calculations
/// ///
/// # Variants /// # Variants
@ -27,35 +23,25 @@ where
/// # Purpose /// # Purpose
/// Provides a type-safe way to specify countries for holiday-related operations /// Provides a type-safe way to specify countries for holiday-related operations
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum CountryCode { pub enum CountryState {
/// Germany /// Germany
DE, DE(GermanState),
/// The United States /// The United States
US, US(USState),
/// France /// France
FR, FR(FrenchState),
}
impl CountryCode {
pub(crate) fn get_holidays(&self) -> (String, Vec<Holiday>) {
match self {
CountryCode::DE => GermanHolidays::new(),
CountryCode::US => USHolidays::new(),
CountryCode::FR => FrenchHolidays::new(),
}
}
} }
pub(crate) trait StateList pub(crate) trait StateList
where where
Self: Sized + Display + Hash + Eq, Self: Sized + Display + Hash + Eq + Clone + Copy,
{ {
fn list(states: &[(Self, &[Activity])]) -> HashMap<String, Vec<Activity>> { fn list(states: &[(Self, &[Activity])]) -> HashMap<Self, Vec<Activity>> {
let mut map = HashMap::new(); let mut map = HashMap::new();
for state in states { for state in states {
map.insert(state.0.to_string(), state.1.to_vec()); map.insert(state.0, state.1.to_vec());
} }
map map
} }

View File

@ -1,14 +1,14 @@
use time::{Date, Month, Weekday}; use time::{Date, Month, Weekday};
use crate::{ use crate::{
countries::{CountryHolidays, StateList}, countries::StateList,
holiday::{Activity, HDate, Holiday}, holiday::{Activity, HDate, Holiday},
utils, utils,
}; };
pub(super) struct USHolidays; pub(crate) struct USHolidays(pub Vec<Holiday<USState>>);
#[derive(strum::Display, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, strum::Display, Hash, PartialEq, Eq)]
pub enum USState { pub enum USState {
// Northeast // Northeast
Maine, Maine,
@ -82,93 +82,91 @@ impl super::StateList for USState {
} }
} }
impl CountryHolidays<USState> for USHolidays { impl USHolidays {
fn new() -> (String, Vec<Holiday>) { pub(crate) fn new() -> Self {
use USState::*; use USState::*;
use time::Month::*; use time::Month::*;
(
USState::all_states_identifier(),
vec![
// NATIONAL HOLIDAYS
// New Years Day Self(vec![
Holiday { // NATIONAL HOLIDAYS
name: "New Year's Day".to_string(),
date: HDate::Fixed(January, 1), // New Years Day
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "New Year's Day".to_string(),
// Birthday of Martin Luther King, Jr. (Third Monday in January) date: HDate::Fixed(January, 1),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Martin Luther King, Jr. Day".to_string(), },
date: HDate::Calculated(|year| { // Birthday of Martin Luther King, Jr. (Third Monday in January)
utils::nth_weekday_in_month(year, Month::January, Weekday::Monday, 3) Holiday {
}), name: "Martin Luther King, Jr. Day".to_string(),
states: USState::list(&[(ANY, &[Activity::unlimited()])]), date: HDate::Calculated(|year| {
}, utils::nth_weekday_in_month(year, Month::January, Weekday::Monday, 3)
// Inauguration Day (January 20, every 4 years following a presidential election) }),
// Washington's Birthday (Also known as Presidents Day; third Monday in February) states: USState::list(&[(ANY, &[Activity::unlimited()])]),
Holiday { },
name: "Washington's Birthday".to_string(), // Inauguration Day (January 20, every 4 years following a presidential election)
date: HDate::Calculated(|year| { // Washington's Birthday (Also known as Presidents Day; third Monday in February)
utils::nth_weekday_in_month(year, Month::February, Weekday::Monday, 3) Holiday {
}), name: "Washington's Birthday".to_string(),
states: USState::list(&[(ANY, &[Activity::unlimited()])]), date: HDate::Calculated(|year| {
}, utils::nth_weekday_in_month(year, Month::February, Weekday::Monday, 3)
// Memorial Day (Last Monday in May) }),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Memorial Day".to_string(), },
date: HDate::Calculated(|year| utils::last_monday_in_month(year, Month::May)), // Memorial Day (Last Monday in May)
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "Memorial Day".to_string(),
// Juneteenth National Independence Day (June 19) date: HDate::Calculated(|year| utils::last_monday_in_month(year, Month::May)),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Juneteenth".to_string(), },
date: HDate::Fixed(Month::June, 19), // Juneteenth National Independence Day (June 19)
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "Juneteenth".to_string(),
// Independence Day (July 4) date: HDate::Fixed(Month::June, 19),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Independence Day".to_string(), },
date: HDate::Fixed(Month::July, 4), // Independence Day (July 4)
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "Independence Day".to_string(),
// Labor Day (First Monday in September) date: HDate::Fixed(Month::July, 4),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Labor Day".to_string(), },
date: HDate::Calculated(|year| { // Labor Day (First Monday in September)
utils::nth_weekday_in_month(year, Month::September, Weekday::Monday, 1) Holiday {
}), name: "Labor Day".to_string(),
states: USState::list(&[(ANY, &[Activity::unlimited()])]), date: HDate::Calculated(|year| {
}, utils::nth_weekday_in_month(year, Month::September, Weekday::Monday, 1)
// Columbus Day (Second Monday in October) }),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Columbus Day".to_string(), },
date: HDate::Calculated(|year| { // Columbus Day (Second Monday in October)
utils::nth_weekday_in_month(year, Month::October, Weekday::Monday, 2) Holiday {
}), name: "Columbus Day".to_string(),
states: USState::list(&[(ANY, &[Activity::unlimited()])]), date: HDate::Calculated(|year| {
}, utils::nth_weekday_in_month(year, Month::October, Weekday::Monday, 2)
// Veterans Day (November 11) }),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Veterans Day".to_string(), },
date: HDate::Fixed(Month::November, 11), // Veterans Day (November 11)
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "Veterans Day".to_string(),
// Thanksgiving Day (Fourth Thursday in November) date: HDate::Fixed(Month::November, 11),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Thanksgiving Day".to_string(), },
date: HDate::Calculated(|year| { // Thanksgiving Day (Fourth Thursday in November)
utils::nth_weekday_in_month(year, Month::November, Weekday::Thursday, 4) Holiday {
}), name: "Thanksgiving Day".to_string(),
states: USState::list(&[(ANY, &[Activity::unlimited()])]), date: HDate::Calculated(|year| {
}, utils::nth_weekday_in_month(year, Month::November, Weekday::Thursday, 4)
// Christmas Day (December 25) }),
Holiday { states: USState::list(&[(ANY, &[Activity::unlimited()])]),
name: "Christmas Day".to_string(), },
date: HDate::Fixed(Month::December, 25), // Christmas Day (December 25)
states: USState::list(&[(ANY, &[Activity::unlimited()])]), Holiday {
}, name: "Christmas Day".to_string(),
], date: HDate::Fixed(Month::December, 25),
) states: USState::list(&[(ANY, &[Activity::unlimited()])]),
},
])
} }
} }

View File

@ -1,7 +1,9 @@
use std::collections::HashMap; use std::{collections::HashMap, hash::Hash};
use time::{Date, Month}; use time::{Date, Month};
use crate::HolidayDate;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Activity { pub(crate) struct Activity {
unlimited: bool, unlimited: bool,
@ -49,22 +51,49 @@ pub(crate) enum HDate {
Calculated(fn(i32) -> Date), Calculated(fn(i32) -> Date),
} }
#[derive(Debug, Clone)] impl HDate {
pub(crate) struct Holiday { pub(crate) fn to_date(&self, year: i32, name: &str) -> HolidayDate {
pub(crate) name: String, match self {
pub(crate) date: HDate, HDate::Calculated(calc_fn) => {
pub(crate) states: HashMap<String, Vec<Activity>>, // 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 { #[derive(Debug, Clone)]
pub(crate) fn is_active(&self, year: i32, all_states_identifier: &str, state: &str) -> bool { pub(crate) struct Holiday<T>
if let Some(all_states) = self.states.get(all_states_identifier) { where
T: Hash + Eq,
{
pub(crate) name: String,
pub(crate) date: HDate,
pub(crate) states: HashMap<T, Vec<Activity>>,
}
impl<T> Holiday<T>
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 holiday is valid for all states and is active in that year
if all_states.iter().any(|activity| activity.is_active(year)) { if all_states.iter().any(|activity| activity.is_active(year)) {
return true; 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 holiday is only valid for this states and is active in that year
if all_states.iter().any(|activity| activity.is_active(year)) { if all_states.iter().any(|activity| activity.is_active(year)) {
return true; return true;

View File

@ -1,18 +1,21 @@
use std::collections::HashMap;
pub use time::Date; 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}; use crate::holiday::{HDate, Holiday};
mod countries; mod countries;
mod holiday; mod holiday;
pub(crate) mod utils; pub(crate) mod utils;
pub use countries::CountryCode; use countries::CountryState;
/// A struct to manage and check holidays for different countries /// A struct to manage and check holidays for different countries
pub struct HolidayChecker { pub struct HolidayChecker {
countries: HashMap<CountryCode, (String, Vec<Holiday>)>, de: GermanHolidays,
us: USHolidays,
fr: FrenchHolidays,
} }
impl HolidayChecker { impl HolidayChecker {
@ -23,15 +26,12 @@ impl HolidayChecker {
/// ///
/// # Returns /// # Returns
/// A new HolidayChecker instance with holidays for the specified countries /// A new HolidayChecker instance with holidays for the specified countries
pub fn new(countries: Vec<CountryCode>) -> Self { pub fn new() -> Self {
let mut map = HashMap::new(); Self {
de: GermanHolidays::new(),
for country in countries { fr: FrenchHolidays::new(),
let holidays = country.get_holidays(); us: USHolidays::new(),
map.insert(country, holidays);
} }
Self { countries: map }
} }
/// Retrieves a list of holidays for a specific country, state, and year /// Retrieves a list of holidays for a specific country, state, and year
@ -43,14 +43,8 @@ impl HolidayChecker {
/// ///
/// # Returns /// # Returns
/// A vector of HolidayDate for the specified country, state, and year /// A vector of HolidayDate for the specified country, state, and year
pub fn holiday_list(&self, country: CountryCode, state: &str, year: i32) -> Vec<HolidayDate> { pub fn holiday_list(&self, country: CountryState, year: i32) -> Vec<HolidayDate> {
Self::get_years_holidays( self.get_years_holidays(country, year)
self.countries
.get(&country)
.unwrap_or(&(String::new(), vec![])),
year,
state,
)
} }
/// Counts the number of holidays for a specific country, state, and year /// Counts the number of holidays for a specific country, state, and year
@ -62,15 +56,8 @@ impl HolidayChecker {
/// ///
/// # Returns /// # Returns
/// The number of holidays for the specified country, state, and year /// The number of holidays for the specified country, state, and year
pub fn number_of_holidays(&self, country: CountryCode, state: &str, year: i32) -> usize { pub fn number_of_holidays(&self, country: CountryState, year: i32) -> usize {
Self::get_years_holidays( self.get_years_holidays(country, year).len()
self.countries
.get(&country)
.unwrap_or(&(String::new(), vec![])),
year,
state,
)
.len()
} }
/// Checks if a specific date is a holiday for a given country, state, and year /// Checks if a specific date is a holiday for a given country, state, and year
@ -83,26 +70,14 @@ impl HolidayChecker {
/// ///
/// # Returns /// # Returns
/// An Option containing the HolidayDate if the date is a holiday, None otherwise /// An Option containing the HolidayDate if the date is a holiday, None otherwise
pub fn is_holiday( pub fn is_holiday(&self, country: CountryState, year: i32, date: Date) -> Option<HolidayDate> {
&self, self.get_years_holidays(country, year)
country: CountryCode, .iter()
state: &str, .find(|holiday| holiday.date == date)
year: i32, .map(|holiday| HolidayDate {
date: Date, name: holiday.name.clone(),
) -> Option<HolidayDate> { date,
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,
})
} }
/// Internal method to retrieve holidays for a specific year and state /// Internal method to retrieve holidays for a specific year and state
@ -114,37 +89,71 @@ impl HolidayChecker {
/// ///
/// # Returns /// # Returns
/// A vector of HolidayDate for the specified year and state /// A vector of HolidayDate for the specified year and state
fn get_years_holidays( fn get_years_holidays(&self, country: CountryState, year: i32) -> Vec<HolidayDate> {
holidays: &(String, Vec<Holiday>), match country {
year: i32, CountryState::DE(state) => self
state: &str, .de
) -> Vec<HolidayDate> { .0
let (all_states_identifier, holidays) = holidays; .iter()
holidays .filter_map(|holiday| {
.iter() // check if holiday is active
.filter_map(|holiday| { if !holiday.is_active(year, GermanState::ANY, state) {
// check if holiday is active return None;
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) => { Some(holiday.date.to_date(year, &holiday.name))
// For fixed date holidays })
Some(HolidayDate { .collect(),
date: Date::from_calendar_date(year, month, day).unwrap(), CountryState::US(state) => self
name: holiday.name.clone(), .us
}) .0
.iter()
.filter_map(|holiday| {
// check if holiday is active
if !holiday.is_active(year, USState::ANY, state) {
return None;
} }
} Some(holiday.date.to_date(year, &holiday.name))
}) })
.collect() .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()
} }
} }