Skip to main content

datasynth_core/distributions/
holidays.rs

1//! Regional holiday calendars for transaction generation.
2//!
3//! Supports holidays for US, DE (Germany), GB (UK), CN (China),
4//! JP (Japan), and IN (India) with appropriate activity multipliers.
5
6use chrono::{Datelike, Duration, NaiveDate, Weekday};
7use serde::{Deserialize, Serialize};
8
9/// Supported regions for holiday calendars.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "UPPERCASE")]
12pub enum Region {
13    /// United States
14    US,
15    /// Germany
16    DE,
17    /// United Kingdom
18    GB,
19    /// China
20    CN,
21    /// Japan
22    JP,
23    /// India
24    IN,
25    /// Brazil
26    BR,
27    /// Mexico
28    MX,
29    /// Australia
30    AU,
31    /// Singapore
32    SG,
33    /// South Korea
34    KR,
35}
36
37impl std::fmt::Display for Region {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Region::US => write!(f, "United States"),
41            Region::DE => write!(f, "Germany"),
42            Region::GB => write!(f, "United Kingdom"),
43            Region::CN => write!(f, "China"),
44            Region::JP => write!(f, "Japan"),
45            Region::IN => write!(f, "India"),
46            Region::BR => write!(f, "Brazil"),
47            Region::MX => write!(f, "Mexico"),
48            Region::AU => write!(f, "Australia"),
49            Region::SG => write!(f, "Singapore"),
50            Region::KR => write!(f, "South Korea"),
51        }
52    }
53}
54
55/// A holiday with its associated activity multiplier.
56#[derive(Debug, Clone)]
57pub struct Holiday {
58    /// Holiday name.
59    pub name: String,
60    /// Date of the holiday.
61    pub date: NaiveDate,
62    /// Activity multiplier (0.0 = completely closed, 1.0 = normal).
63    pub activity_multiplier: f64,
64    /// Whether this is a bank holiday (affects financial transactions).
65    pub is_bank_holiday: bool,
66}
67
68impl Holiday {
69    /// Create a new holiday.
70    pub fn new(name: impl Into<String>, date: NaiveDate, multiplier: f64) -> Self {
71        Self {
72            name: name.into(),
73            date,
74            activity_multiplier: multiplier,
75            is_bank_holiday: true,
76        }
77    }
78
79    /// Set whether this is a bank holiday.
80    pub fn with_bank_holiday(mut self, is_bank_holiday: bool) -> Self {
81        self.is_bank_holiday = is_bank_holiday;
82        self
83    }
84}
85
86/// A calendar of holidays for a specific region and year.
87#[derive(Debug, Clone)]
88pub struct HolidayCalendar {
89    /// Region for this calendar.
90    pub region: Region,
91    /// Year for this calendar.
92    pub year: i32,
93    /// List of holidays.
94    pub holidays: Vec<Holiday>,
95}
96
97impl HolidayCalendar {
98    /// Create a new empty holiday calendar.
99    pub fn new(region: Region, year: i32) -> Self {
100        Self {
101            region,
102            year,
103            holidays: Vec::new(),
104        }
105    }
106
107    /// Create a holiday calendar for a specific region and year.
108    pub fn for_region(region: Region, year: i32) -> Self {
109        match region {
110            Region::US => Self::us_holidays(year),
111            Region::DE => Self::de_holidays(year),
112            Region::GB => Self::gb_holidays(year),
113            Region::CN => Self::cn_holidays(year),
114            Region::JP => Self::jp_holidays(year),
115            Region::IN => Self::in_holidays(year),
116            Region::BR => Self::br_holidays(year),
117            Region::MX => Self::mx_holidays(year),
118            Region::AU => Self::au_holidays(year),
119            Region::SG => Self::sg_holidays(year),
120            Region::KR => Self::kr_holidays(year),
121        }
122    }
123
124    /// Check if a date is a holiday.
125    pub fn is_holiday(&self, date: NaiveDate) -> bool {
126        self.holidays.iter().any(|h| h.date == date)
127    }
128
129    /// Get the activity multiplier for a date.
130    pub fn get_multiplier(&self, date: NaiveDate) -> f64 {
131        self.holidays
132            .iter()
133            .find(|h| h.date == date)
134            .map(|h| h.activity_multiplier)
135            .unwrap_or(1.0)
136    }
137
138    /// Get all holidays for a date (may include multiple on same day).
139    pub fn get_holidays(&self, date: NaiveDate) -> Vec<&Holiday> {
140        self.holidays.iter().filter(|h| h.date == date).collect()
141    }
142
143    /// Add a holiday to the calendar.
144    pub fn add_holiday(&mut self, holiday: Holiday) {
145        self.holidays.push(holiday);
146    }
147
148    /// Get all dates in the calendar.
149    pub fn all_dates(&self) -> Vec<NaiveDate> {
150        self.holidays.iter().map(|h| h.date).collect()
151    }
152
153    /// US Federal Holidays.
154    fn us_holidays(year: i32) -> Self {
155        let mut cal = Self::new(Region::US, year);
156
157        // New Year's Day - Jan 1 (observed)
158        let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
159        cal.add_holiday(Holiday::new(
160            "New Year's Day",
161            Self::observe_weekend(new_years),
162            0.02,
163        ));
164
165        // Martin Luther King Jr. Day - 3rd Monday of January
166        let mlk = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 3);
167        cal.add_holiday(Holiday::new("Martin Luther King Jr. Day", mlk, 0.1));
168
169        // Presidents' Day - 3rd Monday of February
170        let presidents = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 3);
171        cal.add_holiday(Holiday::new("Presidents' Day", presidents, 0.1));
172
173        // Memorial Day - Last Monday of May
174        let memorial = Self::last_weekday_of_month(year, 5, Weekday::Mon);
175        cal.add_holiday(Holiday::new("Memorial Day", memorial, 0.05));
176
177        // Juneteenth - June 19
178        let juneteenth = NaiveDate::from_ymd_opt(year, 6, 19).expect("valid date components");
179        cal.add_holiday(Holiday::new(
180            "Juneteenth",
181            Self::observe_weekend(juneteenth),
182            0.1,
183        ));
184
185        // Independence Day - July 4
186        let independence = NaiveDate::from_ymd_opt(year, 7, 4).expect("valid date components");
187        cal.add_holiday(Holiday::new(
188            "Independence Day",
189            Self::observe_weekend(independence),
190            0.02,
191        ));
192
193        // Labor Day - 1st Monday of September
194        let labor = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 1);
195        cal.add_holiday(Holiday::new("Labor Day", labor, 0.05));
196
197        // Columbus Day - 2nd Monday of October
198        let columbus = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
199        cal.add_holiday(Holiday::new("Columbus Day", columbus, 0.2));
200
201        // Veterans Day - November 11
202        let veterans = NaiveDate::from_ymd_opt(year, 11, 11).expect("valid date components");
203        cal.add_holiday(Holiday::new(
204            "Veterans Day",
205            Self::observe_weekend(veterans),
206            0.1,
207        ));
208
209        // Thanksgiving - 4th Thursday of November
210        let thanksgiving = Self::nth_weekday_of_month(year, 11, Weekday::Thu, 4);
211        cal.add_holiday(Holiday::new("Thanksgiving", thanksgiving, 0.02));
212
213        // Day after Thanksgiving
214        cal.add_holiday(Holiday::new(
215            "Day after Thanksgiving",
216            thanksgiving + Duration::days(1),
217            0.1,
218        ));
219
220        // Christmas Eve - December 24
221        let christmas_eve = NaiveDate::from_ymd_opt(year, 12, 24).expect("valid date components");
222        cal.add_holiday(Holiday::new("Christmas Eve", christmas_eve, 0.1));
223
224        // Christmas Day - December 25
225        let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
226        cal.add_holiday(Holiday::new(
227            "Christmas Day",
228            Self::observe_weekend(christmas),
229            0.02,
230        ));
231
232        // New Year's Eve - December 31
233        let new_years_eve = NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components");
234        cal.add_holiday(Holiday::new("New Year's Eve", new_years_eve, 0.1));
235
236        cal
237    }
238
239    /// German holidays (nationwide).
240    fn de_holidays(year: i32) -> Self {
241        let mut cal = Self::new(Region::DE, year);
242
243        // Neujahr - January 1
244        cal.add_holiday(Holiday::new(
245            "Neujahr",
246            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
247            0.02,
248        ));
249
250        // Karfreitag - Good Friday (Easter - 2 days)
251        let easter = Self::easter_date(year);
252        cal.add_holiday(Holiday::new("Karfreitag", easter - Duration::days(2), 0.02));
253
254        // Ostermontag - Easter Monday
255        cal.add_holiday(Holiday::new(
256            "Ostermontag",
257            easter + Duration::days(1),
258            0.02,
259        ));
260
261        // Tag der Arbeit - May 1
262        cal.add_holiday(Holiday::new(
263            "Tag der Arbeit",
264            NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
265            0.02,
266        ));
267
268        // Christi Himmelfahrt - Ascension Day (Easter + 39 days)
269        cal.add_holiday(Holiday::new(
270            "Christi Himmelfahrt",
271            easter + Duration::days(39),
272            0.02,
273        ));
274
275        // Pfingstmontag - Whit Monday (Easter + 50 days)
276        cal.add_holiday(Holiday::new(
277            "Pfingstmontag",
278            easter + Duration::days(50),
279            0.02,
280        ));
281
282        // Tag der Deutschen Einheit - October 3
283        cal.add_holiday(Holiday::new(
284            "Tag der Deutschen Einheit",
285            NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
286            0.02,
287        ));
288
289        // Weihnachten - December 25-26
290        cal.add_holiday(Holiday::new(
291            "1. Weihnachtstag",
292            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
293            0.02,
294        ));
295        cal.add_holiday(Holiday::new(
296            "2. Weihnachtstag",
297            NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components"),
298            0.02,
299        ));
300
301        // Silvester - December 31
302        cal.add_holiday(Holiday::new(
303            "Silvester",
304            NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components"),
305            0.1,
306        ));
307
308        cal
309    }
310
311    /// UK bank holidays.
312    fn gb_holidays(year: i32) -> Self {
313        let mut cal = Self::new(Region::GB, year);
314
315        // New Year's Day
316        let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
317        cal.add_holiday(Holiday::new(
318            "New Year's Day",
319            Self::observe_weekend(new_years),
320            0.02,
321        ));
322
323        // Good Friday
324        let easter = Self::easter_date(year);
325        cal.add_holiday(Holiday::new(
326            "Good Friday",
327            easter - Duration::days(2),
328            0.02,
329        ));
330
331        // Easter Monday
332        cal.add_holiday(Holiday::new(
333            "Easter Monday",
334            easter + Duration::days(1),
335            0.02,
336        ));
337
338        // Early May Bank Holiday - 1st Monday of May
339        let early_may = Self::nth_weekday_of_month(year, 5, Weekday::Mon, 1);
340        cal.add_holiday(Holiday::new("Early May Bank Holiday", early_may, 0.02));
341
342        // Spring Bank Holiday - Last Monday of May
343        let spring = Self::last_weekday_of_month(year, 5, Weekday::Mon);
344        cal.add_holiday(Holiday::new("Spring Bank Holiday", spring, 0.02));
345
346        // Summer Bank Holiday - Last Monday of August
347        let summer = Self::last_weekday_of_month(year, 8, Weekday::Mon);
348        cal.add_holiday(Holiday::new("Summer Bank Holiday", summer, 0.02));
349
350        // Christmas Day
351        let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
352        cal.add_holiday(Holiday::new(
353            "Christmas Day",
354            Self::observe_weekend(christmas),
355            0.02,
356        ));
357
358        // Boxing Day
359        let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
360        cal.add_holiday(Holiday::new(
361            "Boxing Day",
362            Self::observe_weekend(boxing),
363            0.02,
364        ));
365
366        cal
367    }
368
369    /// Chinese holidays (simplified - fixed dates only).
370    fn cn_holidays(year: i32) -> Self {
371        let mut cal = Self::new(Region::CN, year);
372
373        // New Year's Day - January 1
374        cal.add_holiday(Holiday::new(
375            "New Year",
376            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
377            0.05,
378        ));
379
380        // Spring Festival (Chinese New Year) - approximate late Jan/early Feb
381        // Using a simplified calculation - typically 7-day holiday
382        let cny = Self::approximate_chinese_new_year(year);
383        for i in 0..7 {
384            cal.add_holiday(Holiday::new(
385                if i == 0 {
386                    "Spring Festival"
387                } else {
388                    "Spring Festival Holiday"
389                },
390                cny + Duration::days(i),
391                0.02,
392            ));
393        }
394
395        // Qingming Festival - April 4-6 (approximate)
396        cal.add_holiday(Holiday::new(
397            "Qingming Festival",
398            NaiveDate::from_ymd_opt(year, 4, 5).expect("valid date components"),
399            0.05,
400        ));
401
402        // Labor Day - May 1 (3-day holiday)
403        for i in 0..3 {
404            cal.add_holiday(Holiday::new(
405                if i == 0 {
406                    "Labor Day"
407                } else {
408                    "Labor Day Holiday"
409                },
410                NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components")
411                    + Duration::days(i),
412                0.05,
413            ));
414        }
415
416        // Dragon Boat Festival - approximate early June
417        cal.add_holiday(Holiday::new(
418            "Dragon Boat Festival",
419            NaiveDate::from_ymd_opt(year, 6, 10).expect("valid date components"),
420            0.05,
421        ));
422
423        // Mid-Autumn Festival - approximate late September
424        cal.add_holiday(Holiday::new(
425            "Mid-Autumn Festival",
426            NaiveDate::from_ymd_opt(year, 9, 15).expect("valid date components"),
427            0.05,
428        ));
429
430        // National Day - October 1 (7-day holiday)
431        for i in 0..7 {
432            cal.add_holiday(Holiday::new(
433                if i == 0 {
434                    "National Day"
435                } else {
436                    "National Day Holiday"
437                },
438                NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components")
439                    + Duration::days(i),
440                0.02,
441            ));
442        }
443
444        cal
445    }
446
447    /// Japanese holidays.
448    fn jp_holidays(year: i32) -> Self {
449        let mut cal = Self::new(Region::JP, year);
450
451        // Ganjitsu - January 1
452        cal.add_holiday(Holiday::new(
453            "Ganjitsu (New Year)",
454            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
455            0.02,
456        ));
457
458        // New Year holidays - January 2-3
459        cal.add_holiday(Holiday::new(
460            "New Year Holiday",
461            NaiveDate::from_ymd_opt(year, 1, 2).expect("valid date components"),
462            0.05,
463        ));
464        cal.add_holiday(Holiday::new(
465            "New Year Holiday",
466            NaiveDate::from_ymd_opt(year, 1, 3).expect("valid date components"),
467            0.05,
468        ));
469
470        // Seijin no Hi - Coming of Age Day - 2nd Monday of January
471        let seijin = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 2);
472        cal.add_holiday(Holiday::new("Seijin no Hi", seijin, 0.05));
473
474        // Kenkoku Kinen no Hi - National Foundation Day - February 11
475        cal.add_holiday(Holiday::new(
476            "Kenkoku Kinen no Hi",
477            NaiveDate::from_ymd_opt(year, 2, 11).expect("valid date components"),
478            0.02,
479        ));
480
481        // Tenno Tanjobi - Emperor's Birthday - February 23
482        cal.add_holiday(Holiday::new(
483            "Tenno Tanjobi",
484            NaiveDate::from_ymd_opt(year, 2, 23).expect("valid date components"),
485            0.02,
486        ));
487
488        // Shunbun no Hi - Vernal Equinox - around March 20-21
489        cal.add_holiday(Holiday::new(
490            "Shunbun no Hi",
491            NaiveDate::from_ymd_opt(year, 3, 20).expect("valid date components"),
492            0.02,
493        ));
494
495        // Showa no Hi - Showa Day - April 29
496        cal.add_holiday(Holiday::new(
497            "Showa no Hi",
498            NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
499            0.02,
500        ));
501
502        // Golden Week - April 29 - May 5
503        cal.add_holiday(Holiday::new(
504            "Kenpo Kinenbi",
505            NaiveDate::from_ymd_opt(year, 5, 3).expect("valid date components"),
506            0.02,
507        ));
508        cal.add_holiday(Holiday::new(
509            "Midori no Hi",
510            NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
511            0.02,
512        ));
513        cal.add_holiday(Holiday::new(
514            "Kodomo no Hi",
515            NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
516            0.02,
517        ));
518
519        // Umi no Hi - Marine Day - 3rd Monday of July
520        let umi = Self::nth_weekday_of_month(year, 7, Weekday::Mon, 3);
521        cal.add_holiday(Holiday::new("Umi no Hi", umi, 0.05));
522
523        // Yama no Hi - Mountain Day - August 11
524        cal.add_holiday(Holiday::new(
525            "Yama no Hi",
526            NaiveDate::from_ymd_opt(year, 8, 11).expect("valid date components"),
527            0.05,
528        ));
529
530        // Keiro no Hi - Respect for the Aged Day - 3rd Monday of September
531        let keiro = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 3);
532        cal.add_holiday(Holiday::new("Keiro no Hi", keiro, 0.05));
533
534        // Shubun no Hi - Autumnal Equinox - around September 22-23
535        cal.add_holiday(Holiday::new(
536            "Shubun no Hi",
537            NaiveDate::from_ymd_opt(year, 9, 23).expect("valid date components"),
538            0.02,
539        ));
540
541        // Sports Day - 2nd Monday of October
542        let sports = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
543        cal.add_holiday(Holiday::new("Sports Day", sports, 0.05));
544
545        // Bunka no Hi - Culture Day - November 3
546        cal.add_holiday(Holiday::new(
547            "Bunka no Hi",
548            NaiveDate::from_ymd_opt(year, 11, 3).expect("valid date components"),
549            0.02,
550        ));
551
552        // Kinro Kansha no Hi - Labor Thanksgiving Day - November 23
553        cal.add_holiday(Holiday::new(
554            "Kinro Kansha no Hi",
555            NaiveDate::from_ymd_opt(year, 11, 23).expect("valid date components"),
556            0.02,
557        ));
558
559        cal
560    }
561
562    /// Indian holidays (national holidays).
563    fn in_holidays(year: i32) -> Self {
564        let mut cal = Self::new(Region::IN, year);
565
566        // Republic Day - January 26
567        cal.add_holiday(Holiday::new(
568            "Republic Day",
569            NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components"),
570            0.02,
571        ));
572
573        // Holi - approximate March (lunar calendar)
574        cal.add_holiday(Holiday::new(
575            "Holi",
576            NaiveDate::from_ymd_opt(year, 3, 10).expect("valid date components"),
577            0.05,
578        ));
579
580        // Good Friday
581        let easter = Self::easter_date(year);
582        cal.add_holiday(Holiday::new(
583            "Good Friday",
584            easter - Duration::days(2),
585            0.05,
586        ));
587
588        // Independence Day - August 15
589        cal.add_holiday(Holiday::new(
590            "Independence Day",
591            NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
592            0.02,
593        ));
594
595        // Gandhi Jayanti - October 2
596        cal.add_holiday(Holiday::new(
597            "Gandhi Jayanti",
598            NaiveDate::from_ymd_opt(year, 10, 2).expect("valid date components"),
599            0.02,
600        ));
601
602        // Dussehra - approximate October (lunar calendar)
603        cal.add_holiday(Holiday::new(
604            "Dussehra",
605            NaiveDate::from_ymd_opt(year, 10, 15).expect("valid date components"),
606            0.05,
607        ));
608
609        // Diwali - approximate October/November (5-day festival)
610        let diwali = Self::approximate_diwali(year);
611        for i in 0..5 {
612            cal.add_holiday(Holiday::new(
613                match i {
614                    0 => "Dhanteras",
615                    1 => "Naraka Chaturdashi",
616                    2 => "Diwali",
617                    3 => "Govardhan Puja",
618                    _ => "Bhai Dooj",
619                },
620                diwali + Duration::days(i),
621                if i == 2 { 0.02 } else { 0.1 },
622            ));
623        }
624
625        // Christmas - December 25
626        cal.add_holiday(Holiday::new(
627            "Christmas",
628            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
629            0.1,
630        ));
631
632        cal
633    }
634
635    /// Brazilian holidays (national holidays).
636    fn br_holidays(year: i32) -> Self {
637        let mut cal = Self::new(Region::BR, year);
638
639        // Confraternização Universal - January 1
640        cal.add_holiday(Holiday::new(
641            "Confraternização Universal",
642            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
643            0.02,
644        ));
645
646        // Carnaval - Tuesday before Ash Wednesday (47 days before Easter)
647        let easter = Self::easter_date(year);
648        let carnival_tuesday = easter - Duration::days(47);
649        let carnival_monday = carnival_tuesday - Duration::days(1);
650        cal.add_holiday(Holiday::new("Carnaval (Segunda)", carnival_monday, 0.02));
651        cal.add_holiday(Holiday::new("Carnaval (Terça)", carnival_tuesday, 0.02));
652
653        // Sexta-feira Santa - Good Friday
654        cal.add_holiday(Holiday::new(
655            "Sexta-feira Santa",
656            easter - Duration::days(2),
657            0.02,
658        ));
659
660        // Tiradentes - April 21
661        cal.add_holiday(Holiday::new(
662            "Tiradentes",
663            NaiveDate::from_ymd_opt(year, 4, 21).expect("valid date components"),
664            0.02,
665        ));
666
667        // Dia do Trabalho - May 1
668        cal.add_holiday(Holiday::new(
669            "Dia do Trabalho",
670            NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
671            0.02,
672        ));
673
674        // Corpus Christi - 60 days after Easter
675        cal.add_holiday(Holiday::new(
676            "Corpus Christi",
677            easter + Duration::days(60),
678            0.05,
679        ));
680
681        // IndependĂȘncia do Brasil - September 7
682        cal.add_holiday(Holiday::new(
683            "IndependĂȘncia do Brasil",
684            NaiveDate::from_ymd_opt(year, 9, 7).expect("valid date components"),
685            0.02,
686        ));
687
688        // Nossa Senhora Aparecida - October 12
689        cal.add_holiday(Holiday::new(
690            "Nossa Senhora Aparecida",
691            NaiveDate::from_ymd_opt(year, 10, 12).expect("valid date components"),
692            0.02,
693        ));
694
695        // Finados - November 2
696        cal.add_holiday(Holiday::new(
697            "Finados",
698            NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
699            0.02,
700        ));
701
702        // Proclamação da RepĂșblica - November 15
703        cal.add_holiday(Holiday::new(
704            "Proclamação da RepĂșblica",
705            NaiveDate::from_ymd_opt(year, 11, 15).expect("valid date components"),
706            0.02,
707        ));
708
709        // Natal - December 25
710        cal.add_holiday(Holiday::new(
711            "Natal",
712            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
713            0.02,
714        ));
715
716        cal
717    }
718
719    /// Mexican holidays (national holidays).
720    fn mx_holidays(year: i32) -> Self {
721        let mut cal = Self::new(Region::MX, year);
722
723        // Año Nuevo - January 1
724        cal.add_holiday(Holiday::new(
725            "Año Nuevo",
726            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
727            0.02,
728        ));
729
730        // DĂ­a de la ConstituciĂłn - First Monday of February
731        let constitution = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 1);
732        cal.add_holiday(Holiday::new("DĂ­a de la ConstituciĂłn", constitution, 0.02));
733
734        // Natalicio de Benito JuĂĄrez - Third Monday of March
735        let juarez = Self::nth_weekday_of_month(year, 3, Weekday::Mon, 3);
736        cal.add_holiday(Holiday::new("Natalicio de Benito JuĂĄrez", juarez, 0.02));
737
738        // Semana Santa - Holy Thursday and Good Friday
739        let easter = Self::easter_date(year);
740        cal.add_holiday(Holiday::new(
741            "Jueves Santo",
742            easter - Duration::days(3),
743            0.05,
744        ));
745        cal.add_holiday(Holiday::new(
746            "Viernes Santo",
747            easter - Duration::days(2),
748            0.02,
749        ));
750
751        // DĂ­a del Trabajo - May 1
752        cal.add_holiday(Holiday::new(
753            "DĂ­a del Trabajo",
754            NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
755            0.02,
756        ));
757
758        // DĂ­a de la Independencia - September 16
759        cal.add_holiday(Holiday::new(
760            "DĂ­a de la Independencia",
761            NaiveDate::from_ymd_opt(year, 9, 16).expect("valid date components"),
762            0.02,
763        ));
764
765        // DĂ­a de la RevoluciĂłn - Third Monday of November
766        let revolution = Self::nth_weekday_of_month(year, 11, Weekday::Mon, 3);
767        cal.add_holiday(Holiday::new("DĂ­a de la RevoluciĂłn", revolution, 0.02));
768
769        // DĂ­a de Muertos - November 1-2 (not official but widely observed)
770        cal.add_holiday(Holiday::new(
771            "DĂ­a de Muertos",
772            NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
773            0.1,
774        ));
775        cal.add_holiday(Holiday::new(
776            "DĂ­a de Muertos",
777            NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
778            0.1,
779        ));
780
781        // Navidad - December 25
782        cal.add_holiday(Holiday::new(
783            "Navidad",
784            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
785            0.02,
786        ));
787
788        cal
789    }
790
791    /// Australian holidays (national holidays).
792    fn au_holidays(year: i32) -> Self {
793        let mut cal = Self::new(Region::AU, year);
794
795        // New Year's Day - January 1
796        let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
797        cal.add_holiday(Holiday::new(
798            "New Year's Day",
799            Self::observe_weekend(new_years),
800            0.02,
801        ));
802
803        // Australia Day - January 26 (observed)
804        let australia_day = NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components");
805        cal.add_holiday(Holiday::new(
806            "Australia Day",
807            Self::observe_weekend(australia_day),
808            0.02,
809        ));
810
811        // Good Friday
812        let easter = Self::easter_date(year);
813        cal.add_holiday(Holiday::new(
814            "Good Friday",
815            easter - Duration::days(2),
816            0.02,
817        ));
818
819        // Easter Saturday
820        cal.add_holiday(Holiday::new(
821            "Easter Saturday",
822            easter - Duration::days(1),
823            0.02,
824        ));
825
826        // Easter Monday
827        cal.add_holiday(Holiday::new(
828            "Easter Monday",
829            easter + Duration::days(1),
830            0.02,
831        ));
832
833        // ANZAC Day - April 25
834        let anzac = NaiveDate::from_ymd_opt(year, 4, 25).expect("valid date components");
835        cal.add_holiday(Holiday::new("ANZAC Day", anzac, 0.02));
836
837        // Queen's Birthday - Second Monday of June (varies by state, using NSW)
838        let queens_birthday = Self::nth_weekday_of_month(year, 6, Weekday::Mon, 2);
839        cal.add_holiday(Holiday::new("Queen's Birthday", queens_birthday, 0.02));
840
841        // Christmas Day
842        let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
843        cal.add_holiday(Holiday::new(
844            "Christmas Day",
845            Self::observe_weekend(christmas),
846            0.02,
847        ));
848
849        // Boxing Day - December 26
850        let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
851        cal.add_holiday(Holiday::new(
852            "Boxing Day",
853            Self::observe_weekend(boxing),
854            0.02,
855        ));
856
857        cal
858    }
859
860    /// Singaporean holidays (national holidays).
861    fn sg_holidays(year: i32) -> Self {
862        let mut cal = Self::new(Region::SG, year);
863
864        // New Year's Day - January 1
865        cal.add_holiday(Holiday::new(
866            "New Year's Day",
867            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
868            0.02,
869        ));
870
871        // Chinese New Year (2 days) - approximate
872        let cny = Self::approximate_chinese_new_year(year);
873        cal.add_holiday(Holiday::new("Chinese New Year", cny, 0.02));
874        cal.add_holiday(Holiday::new(
875            "Chinese New Year (Day 2)",
876            cny + Duration::days(1),
877            0.02,
878        ));
879
880        // Good Friday
881        let easter = Self::easter_date(year);
882        cal.add_holiday(Holiday::new(
883            "Good Friday",
884            easter - Duration::days(2),
885            0.02,
886        ));
887
888        // Labour Day - May 1
889        cal.add_holiday(Holiday::new(
890            "Labour Day",
891            NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
892            0.02,
893        ));
894
895        // Vesak Day - approximate (full moon in May)
896        let vesak = Self::approximate_vesak(year);
897        cal.add_holiday(Holiday::new("Vesak Day", vesak, 0.02));
898
899        // Hari Raya Puasa - approximate (end of Ramadan)
900        let hari_raya_puasa = Self::approximate_hari_raya_puasa(year);
901        cal.add_holiday(Holiday::new("Hari Raya Puasa", hari_raya_puasa, 0.02));
902
903        // Hari Raya Haji - approximate (Festival of Sacrifice)
904        let hari_raya_haji = Self::approximate_hari_raya_haji(year);
905        cal.add_holiday(Holiday::new("Hari Raya Haji", hari_raya_haji, 0.02));
906
907        // National Day - August 9
908        cal.add_holiday(Holiday::new(
909            "National Day",
910            NaiveDate::from_ymd_opt(year, 8, 9).expect("valid date components"),
911            0.02,
912        ));
913
914        // Deepavali - approximate (October/November)
915        let deepavali = Self::approximate_deepavali(year);
916        cal.add_holiday(Holiday::new("Deepavali", deepavali, 0.02));
917
918        // Christmas Day
919        cal.add_holiday(Holiday::new(
920            "Christmas Day",
921            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
922            0.02,
923        ));
924
925        cal
926    }
927
928    /// South Korean holidays (national holidays).
929    fn kr_holidays(year: i32) -> Self {
930        let mut cal = Self::new(Region::KR, year);
931
932        // New Year's Day - January 1
933        cal.add_holiday(Holiday::new(
934            "Sinjeong",
935            NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
936            0.02,
937        ));
938
939        // Seollal (Korean New Year) - 3 days around lunar new year
940        let seollal = Self::approximate_korean_new_year(year);
941        cal.add_holiday(Holiday::new(
942            "Seollal (Eve)",
943            seollal - Duration::days(1),
944            0.02,
945        ));
946        cal.add_holiday(Holiday::new("Seollal", seollal, 0.02));
947        cal.add_holiday(Holiday::new(
948            "Seollal (Day 2)",
949            seollal + Duration::days(1),
950            0.02,
951        ));
952
953        // Independence Movement Day - March 1
954        cal.add_holiday(Holiday::new(
955            "Samiljeol",
956            NaiveDate::from_ymd_opt(year, 3, 1).expect("valid date components"),
957            0.02,
958        ));
959
960        // Children's Day - May 5
961        cal.add_holiday(Holiday::new(
962            "Eorininal",
963            NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
964            0.02,
965        ));
966
967        // Buddha's Birthday - approximate (8th day of 4th lunar month)
968        let buddha_birthday = Self::approximate_korean_buddha_birthday(year);
969        cal.add_holiday(Holiday::new("Seokgatansinil", buddha_birthday, 0.02));
970
971        // Memorial Day - June 6
972        cal.add_holiday(Holiday::new(
973            "Hyeonchungil",
974            NaiveDate::from_ymd_opt(year, 6, 6).expect("valid date components"),
975            0.02,
976        ));
977
978        // Liberation Day - August 15
979        cal.add_holiday(Holiday::new(
980            "Gwangbokjeol",
981            NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
982            0.02,
983        ));
984
985        // Chuseok (Korean Thanksgiving) - 3 days around harvest moon
986        let chuseok = Self::approximate_chuseok(year);
987        cal.add_holiday(Holiday::new(
988            "Chuseok (Eve)",
989            chuseok - Duration::days(1),
990            0.02,
991        ));
992        cal.add_holiday(Holiday::new("Chuseok", chuseok, 0.02));
993        cal.add_holiday(Holiday::new(
994            "Chuseok (Day 2)",
995            chuseok + Duration::days(1),
996            0.02,
997        ));
998
999        // National Foundation Day - October 3
1000        cal.add_holiday(Holiday::new(
1001            "Gaecheonjeol",
1002            NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1003            0.02,
1004        ));
1005
1006        // Hangul Day - October 9
1007        cal.add_holiday(Holiday::new(
1008            "Hangullal",
1009            NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1010            0.02,
1011        ));
1012
1013        // Christmas - December 25
1014        cal.add_holiday(Holiday::new(
1015            "Seongtanjeol",
1016            NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1017            0.02,
1018        ));
1019
1020        cal
1021    }
1022
1023    /// Calculate Easter date using the anonymous Gregorian algorithm.
1024    fn easter_date(year: i32) -> NaiveDate {
1025        let a = year % 19;
1026        let b = year / 100;
1027        let c = year % 100;
1028        let d = b / 4;
1029        let e = b % 4;
1030        let f = (b + 8) / 25;
1031        let g = (b - f + 1) / 3;
1032        let h = (19 * a + b - d - g + 15) % 30;
1033        let i = c / 4;
1034        let k = c % 4;
1035        let l = (32 + 2 * e + 2 * i - h - k) % 7;
1036        let m = (a + 11 * h + 22 * l) / 451;
1037        let month = (h + l - 7 * m + 114) / 31;
1038        let day = ((h + l - 7 * m + 114) % 31) + 1;
1039
1040        NaiveDate::from_ymd_opt(year, month as u32, day as u32).expect("valid date components")
1041    }
1042
1043    /// Get nth weekday of a month (e.g., 3rd Monday of January).
1044    fn nth_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u32) -> NaiveDate {
1045        let first = NaiveDate::from_ymd_opt(year, month, 1).expect("valid date components");
1046        let first_weekday = first.weekday();
1047
1048        let days_until = (weekday.num_days_from_monday() as i64
1049            - first_weekday.num_days_from_monday() as i64
1050            + 7)
1051            % 7;
1052
1053        first + Duration::days(days_until + (n - 1) as i64 * 7)
1054    }
1055
1056    /// Get last weekday of a month (e.g., last Monday of May).
1057    fn last_weekday_of_month(year: i32, month: u32, weekday: Weekday) -> NaiveDate {
1058        let last = if month == 12 {
1059            NaiveDate::from_ymd_opt(year + 1, 1, 1).expect("valid date components")
1060                - Duration::days(1)
1061        } else {
1062            NaiveDate::from_ymd_opt(year, month + 1, 1).expect("valid date components")
1063                - Duration::days(1)
1064        };
1065
1066        let last_weekday = last.weekday();
1067        let days_back = (last_weekday.num_days_from_monday() as i64
1068            - weekday.num_days_from_monday() as i64
1069            + 7)
1070            % 7;
1071
1072        last - Duration::days(days_back)
1073    }
1074
1075    /// Observe weekend holidays on nearest weekday.
1076    fn observe_weekend(date: NaiveDate) -> NaiveDate {
1077        match date.weekday() {
1078            Weekday::Sat => date - Duration::days(1), // Friday
1079            Weekday::Sun => date + Duration::days(1), // Monday
1080            _ => date,
1081        }
1082    }
1083
1084    /// Approximate Chinese New Year date (simplified calculation).
1085    fn approximate_chinese_new_year(year: i32) -> NaiveDate {
1086        // Chinese New Year falls between Jan 21 and Feb 20
1087        // This is a simplified approximation
1088        let base_year = 2000;
1089        let cny_2000 = NaiveDate::from_ymd_opt(2000, 2, 5).expect("valid date components");
1090
1091        let years_diff = year - base_year;
1092        let lunar_cycle = 29.5306; // days per lunar month
1093        let days_offset = (years_diff as f64 * 12.0 * lunar_cycle) % 365.25;
1094
1095        let mut result = cny_2000 + Duration::days(days_offset as i64);
1096
1097        // Ensure it falls in Jan-Feb range
1098        while result.month() > 2 || (result.month() == 2 && result.day() > 20) {
1099            result -= Duration::days(29);
1100        }
1101        while result.month() < 1 || (result.month() == 1 && result.day() < 21) {
1102            result += Duration::days(29);
1103        }
1104
1105        // Adjust year if needed
1106        if result.year() != year {
1107            result = NaiveDate::from_ymd_opt(year, result.month(), result.day().min(28))
1108                .unwrap_or_else(|| {
1109                    NaiveDate::from_ymd_opt(year, result.month(), 28)
1110                        .expect("valid date components")
1111                });
1112        }
1113
1114        result
1115    }
1116
1117    /// Approximate Diwali date (simplified calculation).
1118    fn approximate_diwali(year: i32) -> NaiveDate {
1119        // Diwali typically falls in October-November
1120        // This is a simplified approximation
1121        match year % 4 {
1122            0 => NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1123            1 => NaiveDate::from_ymd_opt(year, 10, 24).expect("valid date components"),
1124            2 => NaiveDate::from_ymd_opt(year, 11, 12).expect("valid date components"),
1125            _ => NaiveDate::from_ymd_opt(year, 11, 4).expect("valid date components"),
1126        }
1127    }
1128
1129    /// Approximate Vesak Day (Buddha's Birthday in Theravada tradition).
1130    /// Falls on the full moon of the 4th lunar month (usually May).
1131    fn approximate_vesak(year: i32) -> NaiveDate {
1132        // Vesak is typically in May
1133        // Using approximate lunar cycle calculation
1134        let base = match year % 19 {
1135            0 => 18,
1136            1 => 7,
1137            2 => 26,
1138            3 => 15,
1139            4 => 5,
1140            5 => 24,
1141            6 => 13,
1142            7 => 2,
1143            8 => 22,
1144            9 => 11,
1145            10 => 30,
1146            11 => 19,
1147            12 => 8,
1148            13 => 27,
1149            14 => 17,
1150            15 => 6,
1151            16 => 25,
1152            17 => 14,
1153            _ => 3,
1154        };
1155        let month = if base > 20 { 4 } else { 5 };
1156        let day = if base > 20 { base - 10 } else { base };
1157        NaiveDate::from_ymd_opt(year, month, day.clamp(1, 28) as u32)
1158            .expect("valid date components")
1159    }
1160
1161    /// Approximate Hari Raya Puasa (Eid al-Fitr).
1162    /// Based on Islamic lunar calendar (moves ~11 days earlier each year).
1163    fn approximate_hari_raya_puasa(year: i32) -> NaiveDate {
1164        // Islamic calendar moves about 11 days earlier each year
1165        // Base: 2024 Eid al-Fitr was approximately April 10
1166        let base_year = 2024;
1167        let base_date = NaiveDate::from_ymd_opt(2024, 4, 10).expect("valid date components");
1168        let years_diff = year - base_year;
1169        let days_shift = (years_diff as f64 * -10.63) as i64;
1170        let mut result = base_date + Duration::days(days_shift);
1171
1172        // Wrap around to stay in valid range
1173        while result.year() != year {
1174            if result.year() > year {
1175                result -= Duration::days(354); // Islamic lunar year
1176            } else {
1177                result += Duration::days(354);
1178            }
1179        }
1180        result
1181    }
1182
1183    /// Approximate Hari Raya Haji (Eid al-Adha).
1184    /// Approximately 70 days after Hari Raya Puasa.
1185    fn approximate_hari_raya_haji(year: i32) -> NaiveDate {
1186        Self::approximate_hari_raya_puasa(year) + Duration::days(70)
1187    }
1188
1189    /// Approximate Deepavali date (same as Diwali).
1190    fn approximate_deepavali(year: i32) -> NaiveDate {
1191        Self::approximate_diwali(year)
1192    }
1193
1194    /// Approximate Korean New Year (Seollal).
1195    /// Similar to Chinese New Year but may differ by a day.
1196    fn approximate_korean_new_year(year: i32) -> NaiveDate {
1197        Self::approximate_chinese_new_year(year)
1198    }
1199
1200    /// Approximate Korean Buddha's Birthday.
1201    /// 8th day of the 4th lunar month.
1202    fn approximate_korean_buddha_birthday(year: i32) -> NaiveDate {
1203        // Typically falls in late April to late May
1204        match year % 19 {
1205            0 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1206            1 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1207            2 => NaiveDate::from_ymd_opt(year, 5, 23).expect("valid date components"),
1208            3 => NaiveDate::from_ymd_opt(year, 5, 12).expect("valid date components"),
1209            4 => NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1210            5 => NaiveDate::from_ymd_opt(year, 5, 20).expect("valid date components"),
1211            6 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1212            7 => NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
1213            8 => NaiveDate::from_ymd_opt(year, 5, 18).expect("valid date components"),
1214            9 => NaiveDate::from_ymd_opt(year, 5, 7).expect("valid date components"),
1215            10 => NaiveDate::from_ymd_opt(year, 5, 26).expect("valid date components"),
1216            11 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1217            12 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1218            13 => NaiveDate::from_ymd_opt(year, 5, 24).expect("valid date components"),
1219            14 => NaiveDate::from_ymd_opt(year, 5, 13).expect("valid date components"),
1220            15 => NaiveDate::from_ymd_opt(year, 5, 2).expect("valid date components"),
1221            16 => NaiveDate::from_ymd_opt(year, 5, 21).expect("valid date components"),
1222            17 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1223            _ => NaiveDate::from_ymd_opt(year, 4, 30).expect("valid date components"),
1224        }
1225    }
1226
1227    /// Approximate Chuseok (Korean Thanksgiving).
1228    /// 15th day of the 8th lunar month (harvest moon).
1229    fn approximate_chuseok(year: i32) -> NaiveDate {
1230        // Chuseok typically falls in September or early October
1231        match year % 19 {
1232            0 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1233            1 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1234            2 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1235            3 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1236            4 => NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1237            5 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1238            6 => NaiveDate::from_ymd_opt(year, 9, 11).expect("valid date components"),
1239            7 => NaiveDate::from_ymd_opt(year, 9, 30).expect("valid date components"),
1240            8 => NaiveDate::from_ymd_opt(year, 9, 19).expect("valid date components"),
1241            9 => NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1242            10 => NaiveDate::from_ymd_opt(year, 9, 28).expect("valid date components"),
1243            11 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1244            12 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1245            13 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1246            14 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1247            15 => NaiveDate::from_ymd_opt(year, 10, 4).expect("valid date components"),
1248            16 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1249            17 => NaiveDate::from_ymd_opt(year, 9, 12).expect("valid date components"),
1250            _ => NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components"),
1251        }
1252    }
1253}
1254
1255/// Custom holiday configuration for YAML/JSON input.
1256#[derive(Debug, Clone, Serialize, Deserialize)]
1257pub struct CustomHolidayConfig {
1258    /// Holiday name.
1259    pub name: String,
1260    /// Month (1-12).
1261    pub month: u8,
1262    /// Day of month.
1263    pub day: u8,
1264    /// Activity multiplier (optional, defaults to 0.05).
1265    #[serde(default = "default_holiday_multiplier")]
1266    pub activity_multiplier: f64,
1267}
1268
1269fn default_holiday_multiplier() -> f64 {
1270    0.05
1271}
1272
1273impl CustomHolidayConfig {
1274    /// Convert to a Holiday for a specific year.
1275    pub fn to_holiday(&self, year: i32) -> Holiday {
1276        Holiday::new(
1277            &self.name,
1278            NaiveDate::from_ymd_opt(year, self.month as u32, self.day as u32)
1279                .expect("valid date components"),
1280            self.activity_multiplier,
1281        )
1282    }
1283}
1284
1285#[cfg(test)]
1286#[allow(clippy::unwrap_used)]
1287mod tests {
1288    use super::*;
1289
1290    #[test]
1291    fn test_us_holidays() {
1292        let cal = HolidayCalendar::for_region(Region::US, 2024);
1293
1294        // Check some specific holidays exist
1295        let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1296        assert!(cal.is_holiday(christmas));
1297
1298        // Independence Day (observed on Friday since July 4 is Thursday in 2024)
1299        let independence = NaiveDate::from_ymd_opt(2024, 7, 4).unwrap();
1300        assert!(cal.is_holiday(independence));
1301    }
1302
1303    #[test]
1304    fn test_german_holidays() {
1305        let cal = HolidayCalendar::for_region(Region::DE, 2024);
1306
1307        // Tag der Deutschen Einheit - October 3
1308        let unity = NaiveDate::from_ymd_opt(2024, 10, 3).unwrap();
1309        assert!(cal.is_holiday(unity));
1310    }
1311
1312    #[test]
1313    fn test_easter_calculation() {
1314        // Known Easter dates
1315        assert_eq!(
1316            HolidayCalendar::easter_date(2024),
1317            NaiveDate::from_ymd_opt(2024, 3, 31).unwrap()
1318        );
1319        assert_eq!(
1320            HolidayCalendar::easter_date(2025),
1321            NaiveDate::from_ymd_opt(2025, 4, 20).unwrap()
1322        );
1323    }
1324
1325    #[test]
1326    fn test_nth_weekday() {
1327        // 3rd Monday of January 2024
1328        let mlk = HolidayCalendar::nth_weekday_of_month(2024, 1, Weekday::Mon, 3);
1329        assert_eq!(mlk, NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1330
1331        // 4th Thursday of November 2024 (Thanksgiving)
1332        let thanksgiving = HolidayCalendar::nth_weekday_of_month(2024, 11, Weekday::Thu, 4);
1333        assert_eq!(thanksgiving, NaiveDate::from_ymd_opt(2024, 11, 28).unwrap());
1334    }
1335
1336    #[test]
1337    fn test_last_weekday() {
1338        // Last Monday of May 2024 (Memorial Day)
1339        let memorial = HolidayCalendar::last_weekday_of_month(2024, 5, Weekday::Mon);
1340        assert_eq!(memorial, NaiveDate::from_ymd_opt(2024, 5, 27).unwrap());
1341    }
1342
1343    #[test]
1344    fn test_activity_multiplier() {
1345        let cal = HolidayCalendar::for_region(Region::US, 2024);
1346
1347        // Holiday should have low multiplier
1348        let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1349        assert!(cal.get_multiplier(christmas) < 0.1);
1350
1351        // Regular day should be 1.0
1352        let regular = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
1353        assert!((cal.get_multiplier(regular) - 1.0).abs() < 0.01);
1354    }
1355
1356    #[test]
1357    fn test_all_regions_have_holidays() {
1358        let regions = [
1359            Region::US,
1360            Region::DE,
1361            Region::GB,
1362            Region::CN,
1363            Region::JP,
1364            Region::IN,
1365            Region::BR,
1366            Region::MX,
1367            Region::AU,
1368            Region::SG,
1369            Region::KR,
1370        ];
1371
1372        for region in regions {
1373            let cal = HolidayCalendar::for_region(region, 2024);
1374            assert!(
1375                !cal.holidays.is_empty(),
1376                "Region {:?} should have holidays",
1377                region
1378            );
1379        }
1380    }
1381
1382    #[test]
1383    fn test_brazilian_holidays() {
1384        let cal = HolidayCalendar::for_region(Region::BR, 2024);
1385
1386        // IndependĂȘncia do Brasil - September 7
1387        let independence = NaiveDate::from_ymd_opt(2024, 9, 7).unwrap();
1388        assert!(cal.is_holiday(independence));
1389
1390        // Tiradentes - April 21
1391        let tiradentes = NaiveDate::from_ymd_opt(2024, 4, 21).unwrap();
1392        assert!(cal.is_holiday(tiradentes));
1393    }
1394
1395    #[test]
1396    fn test_mexican_holidays() {
1397        let cal = HolidayCalendar::for_region(Region::MX, 2024);
1398
1399        // DĂ­a de la Independencia - September 16
1400        let independence = NaiveDate::from_ymd_opt(2024, 9, 16).unwrap();
1401        assert!(cal.is_holiday(independence));
1402    }
1403
1404    #[test]
1405    fn test_australian_holidays() {
1406        let cal = HolidayCalendar::for_region(Region::AU, 2024);
1407
1408        // ANZAC Day - April 25
1409        let anzac = NaiveDate::from_ymd_opt(2024, 4, 25).unwrap();
1410        assert!(cal.is_holiday(anzac));
1411
1412        // Australia Day - January 26
1413        let australia_day = NaiveDate::from_ymd_opt(2024, 1, 26).unwrap();
1414        assert!(cal.is_holiday(australia_day));
1415    }
1416
1417    #[test]
1418    fn test_singapore_holidays() {
1419        let cal = HolidayCalendar::for_region(Region::SG, 2024);
1420
1421        // National Day - August 9
1422        let national = NaiveDate::from_ymd_opt(2024, 8, 9).unwrap();
1423        assert!(cal.is_holiday(national));
1424    }
1425
1426    #[test]
1427    fn test_korean_holidays() {
1428        let cal = HolidayCalendar::for_region(Region::KR, 2024);
1429
1430        // Liberation Day - August 15
1431        let liberation = NaiveDate::from_ymd_opt(2024, 8, 15).unwrap();
1432        assert!(cal.is_holiday(liberation));
1433
1434        // Hangul Day - October 9
1435        let hangul = NaiveDate::from_ymd_opt(2024, 10, 9).unwrap();
1436        assert!(cal.is_holiday(hangul));
1437    }
1438
1439    #[test]
1440    fn test_chinese_holidays() {
1441        let cal = HolidayCalendar::for_region(Region::CN, 2024);
1442
1443        // National Day - October 1
1444        let national = NaiveDate::from_ymd_opt(2024, 10, 1).unwrap();
1445        assert!(cal.is_holiday(national));
1446    }
1447
1448    #[test]
1449    fn test_japanese_golden_week() {
1450        let cal = HolidayCalendar::for_region(Region::JP, 2024);
1451
1452        // Check Golden Week holidays
1453        let kodomo = NaiveDate::from_ymd_opt(2024, 5, 5).unwrap();
1454        assert!(cal.is_holiday(kodomo));
1455    }
1456}