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