initialisation
This commit is contained in:
commit
2e033fbd90
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
144
Cargo.lock
generated
Normal file
144
Cargo.lock
generated
Normal file
@ -0,0 +1,144 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "holidays"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"strum",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "holidays"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = ["kellnr"]
|
||||
|
||||
[dependencies]
|
||||
time = "0.3.41"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
time = { version = "0.3.41", features = ["macros"] }
|
444
src/countries/de.rs
Normal file
444
src/countries/de.rs
Normal file
@ -0,0 +1,444 @@
|
||||
use time::{Date, Duration, Month, Weekday};
|
||||
|
||||
use crate::{
|
||||
countries::{CountryHolidays, StateList},
|
||||
holiday::{Activity, HDate, Holiday},
|
||||
utils::{self},
|
||||
};
|
||||
|
||||
pub(super) struct GermanHolidays;
|
||||
|
||||
#[derive(strum::Display, Hash, PartialEq, Eq)]
|
||||
pub enum GermanState {
|
||||
/// Baden-Württemberg
|
||||
BW,
|
||||
/// Bayern
|
||||
BY,
|
||||
/// Berlin
|
||||
BE,
|
||||
/// Brandenburg
|
||||
BB,
|
||||
/// Bremen
|
||||
HB,
|
||||
/// Hamburg
|
||||
HH,
|
||||
/// Hessen
|
||||
HE,
|
||||
/// Mecklenburg-Vorpommern
|
||||
MV,
|
||||
/// Niedersachsen
|
||||
NI,
|
||||
/// Nordrhein-Westfalen
|
||||
NW,
|
||||
/// Rheinland-Pfalz
|
||||
RP,
|
||||
/// Saarland
|
||||
SL,
|
||||
/// Sachsen
|
||||
SN,
|
||||
/// Sachsen-Anhalt
|
||||
ST,
|
||||
/// Schleswig-Holstein
|
||||
SH,
|
||||
/// Thüringen
|
||||
TH,
|
||||
/// Checks for holidays in any state
|
||||
ANY,
|
||||
}
|
||||
|
||||
impl super::StateList for GermanState {
|
||||
fn all_states_identifier() -> String {
|
||||
Self::ANY.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl CountryHolidays<GermanState> for GermanHolidays {
|
||||
fn new() -> (String, Vec<Holiday>) {
|
||||
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)])]),
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use time::macros::date;
|
||||
|
||||
use crate::{
|
||||
HolidayChecker,
|
||||
countries::{CountryCode, de::GermanState},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_number_of_holidays() {
|
||||
let checker = HolidayChecker::new(vec![CountryCode::DE]);
|
||||
|
||||
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!(
|
||||
checker.number_of_holidays(CountryCode::DE, &state.to_string(), 2025),
|
||||
num
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_holidays() {
|
||||
let checker = HolidayChecker::new(vec![CountryCode::DE]);
|
||||
let country = CountryCode::DE;
|
||||
let state = GermanState::ANY.to_string();
|
||||
|
||||
// New Years Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 01 - 01))
|
||||
.is_some()
|
||||
);
|
||||
// Epiphany
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 01 - 06))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::BW.to_string(),
|
||||
2025,
|
||||
date!(2025 - 01 - 06)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// International Women's Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 03 - 08))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::BE.to_string(),
|
||||
2025,
|
||||
date!(2025 - 03 - 08)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// Good Friday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 04 - 18))
|
||||
.is_some()
|
||||
);
|
||||
// Easter Monday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 04 - 21))
|
||||
.is_some()
|
||||
);
|
||||
// Labour Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 05 - 01))
|
||||
.is_some()
|
||||
);
|
||||
// Ascension Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 05 - 29))
|
||||
.is_some()
|
||||
);
|
||||
// Whit Monday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 06 - 09))
|
||||
.is_some()
|
||||
);
|
||||
// Corpus Christi
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 06 - 19))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::BW.to_string(),
|
||||
2025,
|
||||
date!(2025 - 06 - 19)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// Assumption Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 08 - 15))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::BY.to_string(),
|
||||
2025,
|
||||
date!(2025 - 08 - 15)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// World Children's Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 09 - 20))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::TH.to_string(),
|
||||
2025,
|
||||
date!(2025 - 09 - 20)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// German Unity Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 10 - 03))
|
||||
.is_some()
|
||||
);
|
||||
// Reformation Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 10 - 31))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::HH.to_string(),
|
||||
2025,
|
||||
date!(2025 - 10 - 31)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// All Saints Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 11 - 01))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::BW.to_string(),
|
||||
2025,
|
||||
date!(2025 - 11 - 01)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// Repentance And Prayer Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 11 - 19))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&GermanState::SN.to_string(),
|
||||
2025,
|
||||
date!(2025 - 11 - 19)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// Christmas Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 12 - 25))
|
||||
.is_some()
|
||||
);
|
||||
// Second Day Of Christmas
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 12 - 26))
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
}
|
259
src/countries/fr.rs
Normal file
259
src/countries/fr.rs
Normal file
@ -0,0 +1,259 @@
|
||||
use time::Duration;
|
||||
|
||||
use crate::{
|
||||
countries::{CountryHolidays, StateList},
|
||||
holiday::{Activity, HDate, Holiday},
|
||||
utils,
|
||||
};
|
||||
|
||||
pub(super) struct FrenchHolidays;
|
||||
|
||||
#[derive(strum::Display, Hash, PartialEq, Eq)]
|
||||
pub enum FrenchState {
|
||||
/// Bas-Rhin
|
||||
BasRhin,
|
||||
/// Haut-Rhin
|
||||
HautRhin,
|
||||
/// Moselle
|
||||
Moselle,
|
||||
/// Checks for holidays in any state
|
||||
ANY,
|
||||
}
|
||||
|
||||
impl super::StateList for FrenchState {
|
||||
fn all_states_identifier() -> String {
|
||||
Self::ANY.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl CountryHolidays<FrenchState> for FrenchHolidays {
|
||||
fn new() -> (String, Vec<Holiday>) {
|
||||
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()]),
|
||||
]),
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use time::macros::date;
|
||||
|
||||
use crate::{
|
||||
HolidayChecker,
|
||||
countries::{CountryCode, fr::FrenchState},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_number_of_holidays() {
|
||||
let checker = HolidayChecker::new(vec![CountryCode::FR]);
|
||||
|
||||
let tests = [
|
||||
(FrenchState::BasRhin, 14),
|
||||
(FrenchState::HautRhin, 14),
|
||||
(FrenchState::Moselle, 14),
|
||||
(FrenchState::ANY, 12),
|
||||
];
|
||||
for (state, num) in tests {
|
||||
assert_eq!(
|
||||
checker.number_of_holidays(CountryCode::FR, &state.to_string(), 2025),
|
||||
num
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_holidays() {
|
||||
let checker = HolidayChecker::new(vec![CountryCode::FR]);
|
||||
let country = CountryCode::FR;
|
||||
let state = FrenchState::ANY.to_string();
|
||||
|
||||
// New Years Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 01 - 01))
|
||||
.is_some()
|
||||
);
|
||||
// Good Friday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 04 - 18))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&FrenchState::BasRhin.to_string(),
|
||||
2025,
|
||||
date!(2025 - 04 - 18)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
// Easter Monday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 04 - 21))
|
||||
.is_some()
|
||||
);
|
||||
// Labour Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 05 - 01))
|
||||
.is_some()
|
||||
);
|
||||
// Victory Day (Second World War)
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 05 - 08))
|
||||
.is_some()
|
||||
);
|
||||
// Ascension Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 05 - 29))
|
||||
.is_some()
|
||||
);
|
||||
// Whit Monday
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 06 - 09))
|
||||
.is_some()
|
||||
);
|
||||
// National Day (14th July)
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 07 - 14))
|
||||
.is_some()
|
||||
);
|
||||
// Assumption Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 08 - 15))
|
||||
.is_some()
|
||||
);
|
||||
// All Saints Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 11 - 01))
|
||||
.is_some()
|
||||
);
|
||||
// Victory Day (First World War)
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 11 - 11))
|
||||
.is_some()
|
||||
);
|
||||
// Christmas Day
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 12 - 25))
|
||||
.is_some()
|
||||
);
|
||||
// Second Day Of Christmas
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(country, &state, 2025, date!(2025 - 12 - 26))
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
checker
|
||||
.is_holiday(
|
||||
country,
|
||||
&FrenchState::BasRhin.to_string(),
|
||||
2025,
|
||||
date!(2025 - 12 - 26)
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
}
|
50
src/countries/mod.rs
Normal file
50
src/countries/mod.rs
Normal file
@ -0,0 +1,50 @@
|
||||
mod de;
|
||||
mod fr;
|
||||
mod us;
|
||||
|
||||
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
||||
|
||||
use crate::{
|
||||
countries::{de::GermanHolidays, fr::FrenchHolidays, us::USHolidays},
|
||||
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>);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum CountryCode {
|
||||
DE,
|
||||
US,
|
||||
FR,
|
||||
}
|
||||
|
||||
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
|
||||
where
|
||||
Self: Sized + Display + Hash + Eq,
|
||||
{
|
||||
fn list(states: &[(Self, &[Activity])]) -> HashMap<String, Vec<Activity>> {
|
||||
let mut map = HashMap::new();
|
||||
for state in states {
|
||||
map.insert(state.0.to_string(), state.1.to_vec());
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn all_states_identifier() -> String;
|
||||
}
|
174
src/countries/us.rs
Normal file
174
src/countries/us.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use time::{Date, Month, Weekday};
|
||||
|
||||
use crate::{
|
||||
countries::{CountryHolidays, StateList},
|
||||
holiday::{Activity, HDate, Holiday},
|
||||
utils,
|
||||
};
|
||||
|
||||
pub(super) struct USHolidays;
|
||||
|
||||
#[derive(strum::Display, Hash, PartialEq, Eq)]
|
||||
pub enum USState {
|
||||
// Northeast
|
||||
Maine,
|
||||
NewHampshire,
|
||||
Vermont,
|
||||
Massachusetts,
|
||||
RhodeIsland,
|
||||
Connecticut,
|
||||
NewYork,
|
||||
NewJersey,
|
||||
Pennsylvania,
|
||||
|
||||
// Southeast
|
||||
Delaware,
|
||||
Maryland,
|
||||
Virginia,
|
||||
WestVirginia,
|
||||
NorthCarolina,
|
||||
SouthCarolina,
|
||||
Georgia,
|
||||
Florida,
|
||||
Kentucky,
|
||||
Tennessee,
|
||||
|
||||
// Midwest
|
||||
Ohio,
|
||||
Michigan,
|
||||
Indiana,
|
||||
Illinois,
|
||||
Wisconsin,
|
||||
Minnesota,
|
||||
Iowa,
|
||||
Missouri,
|
||||
NorthDakota,
|
||||
SouthDakota,
|
||||
Nebraska,
|
||||
Kansas,
|
||||
|
||||
// Southwest
|
||||
Texas,
|
||||
Oklahoma,
|
||||
NewMexico,
|
||||
Arizona,
|
||||
|
||||
// West
|
||||
Colorado,
|
||||
Wyoming,
|
||||
Montana,
|
||||
Idaho,
|
||||
Utah,
|
||||
Nevada,
|
||||
California,
|
||||
Oregon,
|
||||
Washington,
|
||||
Alaska,
|
||||
Hawaii,
|
||||
|
||||
// Additional states
|
||||
Alabama,
|
||||
Arkansas,
|
||||
Mississippi,
|
||||
Louisiana,
|
||||
|
||||
// All states
|
||||
ANY,
|
||||
}
|
||||
|
||||
impl super::StateList for USState {
|
||||
fn all_states_identifier() -> String {
|
||||
Self::ANY.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl CountryHolidays<USState> for USHolidays {
|
||||
fn new() -> (String, Vec<Holiday>) {
|
||||
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()])]),
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
98
src/holiday.rs
Normal file
98
src/holiday.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use time::{Date, Month};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Activity {
|
||||
unlimited: bool,
|
||||
from: i32,
|
||||
to: Option<i32>,
|
||||
}
|
||||
impl Activity {
|
||||
pub(crate) fn unlimited() -> Self {
|
||||
Self {
|
||||
unlimited: true,
|
||||
from: 0,
|
||||
to: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn after(year: i32) -> Self {
|
||||
Self {
|
||||
unlimited: false,
|
||||
from: year,
|
||||
to: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn range(from: i32, to: i32) -> Self {
|
||||
Self {
|
||||
unlimited: false,
|
||||
from,
|
||||
to: Some(to),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self, year: i32) -> bool {
|
||||
self.unlimited
|
||||
|| (year >= self.from
|
||||
&& match self.to {
|
||||
Some(limit) => limit >= year,
|
||||
None => true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum HDate {
|
||||
Fixed(Month, u8),
|
||||
// Optional for holidays with complex calculation (like Easter)
|
||||
Calculated(fn(i32) -> Date),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Holiday {
|
||||
pub(crate) name: String,
|
||||
pub(crate) date: HDate,
|
||||
pub(crate) states: HashMap<String, Vec<Activity>>,
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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 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;
|
||||
}
|
||||
}
|
||||
// holiday is inactive
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HolidayDate {
|
||||
pub name: String,
|
||||
pub date: Date,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::holiday::Activity;
|
||||
|
||||
#[test]
|
||||
fn test_activity() {
|
||||
assert!(Activity::after(2000).is_active(2000));
|
||||
assert!(Activity::after(2000).is_active(2001));
|
||||
assert!(!Activity::after(2000).is_active(1999));
|
||||
|
||||
assert!(Activity::range(2000, 2000).is_active(2000));
|
||||
assert!(!Activity::range(2000, 2000).is_active(1999));
|
||||
assert!(!Activity::range(2000, 2000).is_active(2001));
|
||||
}
|
||||
}
|
105
src/lib.rs
Normal file
105
src/lib.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use time::Date;
|
||||
|
||||
use crate::{
|
||||
countries::CountryCode,
|
||||
holiday::{HDate, Holiday, HolidayDate},
|
||||
};
|
||||
|
||||
mod countries;
|
||||
mod holiday;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub struct HolidayChecker {
|
||||
countries: HashMap<CountryCode, (String, Vec<Holiday>)>,
|
||||
}
|
||||
|
||||
impl HolidayChecker {
|
||||
pub fn new(countries: Vec<CountryCode>) -> Self {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for country in countries {
|
||||
let holidays = country.get_holidays();
|
||||
map.insert(country, holidays);
|
||||
}
|
||||
|
||||
Self { countries: map }
|
||||
}
|
||||
|
||||
pub fn holiday_list(&self, country: CountryCode, state: &str, year: i32) -> Vec<HolidayDate> {
|
||||
Self::get_years_holidays(
|
||||
self.countries
|
||||
.get(&country)
|
||||
.unwrap_or(&(String::new(), vec![])),
|
||||
year,
|
||||
state,
|
||||
)
|
||||
}
|
||||
|
||||
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 is_holiday(
|
||||
&self,
|
||||
country: CountryCode,
|
||||
state: &str,
|
||||
year: i32,
|
||||
date: Date,
|
||||
) -> Option<HolidayDate> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_years_holidays(
|
||||
holidays: &(String, Vec<Holiday>),
|
||||
year: i32,
|
||||
state: &str,
|
||||
) -> Vec<HolidayDate> {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
HDate::Fixed(month, day) => {
|
||||
// For fixed date holidays
|
||||
Some(HolidayDate {
|
||||
date: Date::from_calendar_date(year, month, day).unwrap(),
|
||||
name: holiday.name.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
66
src/utils.rs
Normal file
66
src/utils.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use time::{Date, Duration, Month, Weekday};
|
||||
|
||||
// Calculate Easter Monday (Meeus/Jones/Butcher Algorithm)
|
||||
pub(crate) fn easter_monday(year: i32) -> Date {
|
||||
let a = year % 19;
|
||||
let b = year / 100;
|
||||
let c = year % 100;
|
||||
let d = b / 4;
|
||||
let e = b % 4;
|
||||
let f = (b + 8) / 25;
|
||||
let g = (b - f + 1) / 3;
|
||||
let h = (19 * a + b - d - g + 15) % 30;
|
||||
let i = c / 4;
|
||||
let k = c % 4;
|
||||
let l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||
let m = (a + 11 * h + 22 * l) / 451;
|
||||
|
||||
let month = (h + l - 7 * m + 114) / 31;
|
||||
let day = ((h + l - 7 * m + 114) % 31) + 1;
|
||||
|
||||
// Easter Sunday
|
||||
let easter_sunday =
|
||||
Date::from_calendar_date(year, Month::try_from(month as u8).unwrap(), day as u8).unwrap();
|
||||
|
||||
// Easter Monday is the day after Easter Sunday
|
||||
easter_sunday + Duration::days(1)
|
||||
}
|
||||
|
||||
// Find the nth 'Weekday' in a given month
|
||||
pub(crate) fn nth_weekday_in_month(
|
||||
year: i32,
|
||||
month: Month,
|
||||
weekday: Weekday,
|
||||
occurrence: u8,
|
||||
) -> Date {
|
||||
let first = Date::from_calendar_date(year, month, 1).unwrap();
|
||||
if first.weekday() == weekday {
|
||||
if occurrence == 1 {
|
||||
first
|
||||
} else {
|
||||
first.nth_next_occurrence(weekday, occurrence - 1)
|
||||
}
|
||||
} else {
|
||||
first.nth_next_occurrence(weekday, occurrence)
|
||||
}
|
||||
}
|
||||
|
||||
// Find the last Monday in a given month
|
||||
pub(crate) fn last_monday_in_month(year: i32, month: Month) -> Date {
|
||||
// Get the first day of the next month
|
||||
let first_day_next_month = Date::from_calendar_date(
|
||||
if month == Month::December {
|
||||
year + 1
|
||||
} else {
|
||||
year
|
||||
},
|
||||
month.next(),
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Subtract one day to get the last day of the current month
|
||||
let date = first_day_next_month.previous_day().unwrap();
|
||||
|
||||
date.prev_occurrence(Weekday::Monday)
|
||||
}
|
Loading…
Reference in New Issue
Block a user