1mod data;
8mod calc;
9pub mod export;
10
11use chrono::{NaiveDate, Datelike};
12use serde::{Serialize, Deserialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct Holiday {
16 pub name: String,
17 pub date: NaiveDate,
18 pub country: String,
19}
20
21pub fn get_holidays(year: i32, country: &str) -> Vec<Holiday> {
23 match country.to_uppercase().as_str() {
24 "FR" => data::fr::holidays_fr(year),
25 _ => Vec::new(),
26 }
27}
28
29pub fn next_holiday(date: NaiveDate, country: &str) -> Option<Holiday> {
31 let mut list = get_holidays(date.year(), country);
32 if date.month() == 12 && date.day() >= 20 {
33 list.extend(get_holidays(date.year() + 1, country));
34 }
35 list.into_iter().filter(|h| h.date > date).min_by_key(|h| h.date)
36}
37
38pub fn is_business_day(date: NaiveDate, country: &str) -> bool {
40 let weekday = date.weekday().number_from_monday(); if weekday >= 6 {
42 return false;
43 }
44 let holidays = get_holidays(date.year(), country);
45 !holidays.iter().any(|h| h.date == date)
46}
47
48pub fn business_days_between(start: NaiveDate, end: NaiveDate, country: &str) -> u32 {
50 if start >= end { return 0; }
51 let mut count = 0u32;
52 let mut d = start;
53 while d < end {
54 if is_business_day(d, country) { count += 1; }
55 d = d.succ_opt().unwrap();
56 }
57 count
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use chrono::NaiveDate;
64
65 #[test]
66 fn test_fr_2025_contains_may1() {
67 let v = get_holidays(2025, "FR");
68 assert!(v.iter().any(|h| h.name.contains("FĂȘte du Travail") && h.date == NaiveDate::from_ymd_opt(2025,5,1).unwrap()));
69 }
70
71 #[test]
72 fn test_business_days_between_simple() {
73 let s = NaiveDate::from_ymd_opt(2025, 5, 2).unwrap(); let e = NaiveDate::from_ymd_opt(2025, 5, 6).unwrap(); assert_eq!(business_days_between(s, e, "FR"), 2); }
77}
78