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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap(),
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).unwrap(),
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).unwrap(),
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).unwrap(),
293            0.02,
294        ));
295        cal.add_holiday(Holiday::new(
296            "2. Weihnachtstag",
297            NaiveDate::from_ymd_opt(year, 12, 26).unwrap(),
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).unwrap(),
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap(),
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).unwrap(),
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).unwrap() + Duration::days(i),
411                0.05,
412            ));
413        }
414
415        // Dragon Boat Festival - approximate early June
416        cal.add_holiday(Holiday::new(
417            "Dragon Boat Festival",
418            NaiveDate::from_ymd_opt(year, 6, 10).unwrap(),
419            0.05,
420        ));
421
422        // Mid-Autumn Festival - approximate late September
423        cal.add_holiday(Holiday::new(
424            "Mid-Autumn Festival",
425            NaiveDate::from_ymd_opt(year, 9, 15).unwrap(),
426            0.05,
427        ));
428
429        // National Day - October 1 (7-day holiday)
430        for i in 0..7 {
431            cal.add_holiday(Holiday::new(
432                if i == 0 {
433                    "National Day"
434                } else {
435                    "National Day Holiday"
436                },
437                NaiveDate::from_ymd_opt(year, 10, 1).unwrap() + Duration::days(i),
438                0.02,
439            ));
440        }
441
442        cal
443    }
444
445    /// Japanese holidays.
446    fn jp_holidays(year: i32) -> Self {
447        let mut cal = Self::new(Region::JP, year);
448
449        // Ganjitsu - January 1
450        cal.add_holiday(Holiday::new(
451            "Ganjitsu (New Year)",
452            NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
453            0.02,
454        ));
455
456        // New Year holidays - January 2-3
457        cal.add_holiday(Holiday::new(
458            "New Year Holiday",
459            NaiveDate::from_ymd_opt(year, 1, 2).unwrap(),
460            0.05,
461        ));
462        cal.add_holiday(Holiday::new(
463            "New Year Holiday",
464            NaiveDate::from_ymd_opt(year, 1, 3).unwrap(),
465            0.05,
466        ));
467
468        // Seijin no Hi - Coming of Age Day - 2nd Monday of January
469        let seijin = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 2);
470        cal.add_holiday(Holiday::new("Seijin no Hi", seijin, 0.05));
471
472        // Kenkoku Kinen no Hi - National Foundation Day - February 11
473        cal.add_holiday(Holiday::new(
474            "Kenkoku Kinen no Hi",
475            NaiveDate::from_ymd_opt(year, 2, 11).unwrap(),
476            0.02,
477        ));
478
479        // Tenno Tanjobi - Emperor's Birthday - February 23
480        cal.add_holiday(Holiday::new(
481            "Tenno Tanjobi",
482            NaiveDate::from_ymd_opt(year, 2, 23).unwrap(),
483            0.02,
484        ));
485
486        // Shunbun no Hi - Vernal Equinox - around March 20-21
487        cal.add_holiday(Holiday::new(
488            "Shunbun no Hi",
489            NaiveDate::from_ymd_opt(year, 3, 20).unwrap(),
490            0.02,
491        ));
492
493        // Showa no Hi - Showa Day - April 29
494        cal.add_holiday(Holiday::new(
495            "Showa no Hi",
496            NaiveDate::from_ymd_opt(year, 4, 29).unwrap(),
497            0.02,
498        ));
499
500        // Golden Week - April 29 - May 5
501        cal.add_holiday(Holiday::new(
502            "Kenpo Kinenbi",
503            NaiveDate::from_ymd_opt(year, 5, 3).unwrap(),
504            0.02,
505        ));
506        cal.add_holiday(Holiday::new(
507            "Midori no Hi",
508            NaiveDate::from_ymd_opt(year, 5, 4).unwrap(),
509            0.02,
510        ));
511        cal.add_holiday(Holiday::new(
512            "Kodomo no Hi",
513            NaiveDate::from_ymd_opt(year, 5, 5).unwrap(),
514            0.02,
515        ));
516
517        // Umi no Hi - Marine Day - 3rd Monday of July
518        let umi = Self::nth_weekday_of_month(year, 7, Weekday::Mon, 3);
519        cal.add_holiday(Holiday::new("Umi no Hi", umi, 0.05));
520
521        // Yama no Hi - Mountain Day - August 11
522        cal.add_holiday(Holiday::new(
523            "Yama no Hi",
524            NaiveDate::from_ymd_opt(year, 8, 11).unwrap(),
525            0.05,
526        ));
527
528        // Keiro no Hi - Respect for the Aged Day - 3rd Monday of September
529        let keiro = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 3);
530        cal.add_holiday(Holiday::new("Keiro no Hi", keiro, 0.05));
531
532        // Shubun no Hi - Autumnal Equinox - around September 22-23
533        cal.add_holiday(Holiday::new(
534            "Shubun no Hi",
535            NaiveDate::from_ymd_opt(year, 9, 23).unwrap(),
536            0.02,
537        ));
538
539        // Sports Day - 2nd Monday of October
540        let sports = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
541        cal.add_holiday(Holiday::new("Sports Day", sports, 0.05));
542
543        // Bunka no Hi - Culture Day - November 3
544        cal.add_holiday(Holiday::new(
545            "Bunka no Hi",
546            NaiveDate::from_ymd_opt(year, 11, 3).unwrap(),
547            0.02,
548        ));
549
550        // Kinro Kansha no Hi - Labor Thanksgiving Day - November 23
551        cal.add_holiday(Holiday::new(
552            "Kinro Kansha no Hi",
553            NaiveDate::from_ymd_opt(year, 11, 23).unwrap(),
554            0.02,
555        ));
556
557        cal
558    }
559
560    /// Indian holidays (national holidays).
561    fn in_holidays(year: i32) -> Self {
562        let mut cal = Self::new(Region::IN, year);
563
564        // Republic Day - January 26
565        cal.add_holiday(Holiday::new(
566            "Republic Day",
567            NaiveDate::from_ymd_opt(year, 1, 26).unwrap(),
568            0.02,
569        ));
570
571        // Holi - approximate March (lunar calendar)
572        cal.add_holiday(Holiday::new(
573            "Holi",
574            NaiveDate::from_ymd_opt(year, 3, 10).unwrap(),
575            0.05,
576        ));
577
578        // Good Friday
579        let easter = Self::easter_date(year);
580        cal.add_holiday(Holiday::new(
581            "Good Friday",
582            easter - Duration::days(2),
583            0.05,
584        ));
585
586        // Independence Day - August 15
587        cal.add_holiday(Holiday::new(
588            "Independence Day",
589            NaiveDate::from_ymd_opt(year, 8, 15).unwrap(),
590            0.02,
591        ));
592
593        // Gandhi Jayanti - October 2
594        cal.add_holiday(Holiday::new(
595            "Gandhi Jayanti",
596            NaiveDate::from_ymd_opt(year, 10, 2).unwrap(),
597            0.02,
598        ));
599
600        // Dussehra - approximate October (lunar calendar)
601        cal.add_holiday(Holiday::new(
602            "Dussehra",
603            NaiveDate::from_ymd_opt(year, 10, 15).unwrap(),
604            0.05,
605        ));
606
607        // Diwali - approximate October/November (5-day festival)
608        let diwali = Self::approximate_diwali(year);
609        for i in 0..5 {
610            cal.add_holiday(Holiday::new(
611                match i {
612                    0 => "Dhanteras",
613                    1 => "Naraka Chaturdashi",
614                    2 => "Diwali",
615                    3 => "Govardhan Puja",
616                    _ => "Bhai Dooj",
617                },
618                diwali + Duration::days(i),
619                if i == 2 { 0.02 } else { 0.1 },
620            ));
621        }
622
623        // Christmas - December 25
624        cal.add_holiday(Holiday::new(
625            "Christmas",
626            NaiveDate::from_ymd_opt(year, 12, 25).unwrap(),
627            0.1,
628        ));
629
630        cal
631    }
632
633    /// Brazilian holidays (national holidays).
634    fn br_holidays(year: i32) -> Self {
635        let mut cal = Self::new(Region::BR, year);
636
637        // Confraternização Universal - January 1
638        cal.add_holiday(Holiday::new(
639            "Confraternização Universal",
640            NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
641            0.02,
642        ));
643
644        // Carnaval - Tuesday before Ash Wednesday (47 days before Easter)
645        let easter = Self::easter_date(year);
646        let carnival_tuesday = easter - Duration::days(47);
647        let carnival_monday = carnival_tuesday - Duration::days(1);
648        cal.add_holiday(Holiday::new("Carnaval (Segunda)", carnival_monday, 0.02));
649        cal.add_holiday(Holiday::new("Carnaval (Terça)", carnival_tuesday, 0.02));
650
651        // Sexta-feira Santa - Good Friday
652        cal.add_holiday(Holiday::new(
653            "Sexta-feira Santa",
654            easter - Duration::days(2),
655            0.02,
656        ));
657
658        // Tiradentes - April 21
659        cal.add_holiday(Holiday::new(
660            "Tiradentes",
661            NaiveDate::from_ymd_opt(year, 4, 21).unwrap(),
662            0.02,
663        ));
664
665        // Dia do Trabalho - May 1
666        cal.add_holiday(Holiday::new(
667            "Dia do Trabalho",
668            NaiveDate::from_ymd_opt(year, 5, 1).unwrap(),
669            0.02,
670        ));
671
672        // Corpus Christi - 60 days after Easter
673        cal.add_holiday(Holiday::new(
674            "Corpus Christi",
675            easter + Duration::days(60),
676            0.05,
677        ));
678
679        // IndependĂȘncia do Brasil - September 7
680        cal.add_holiday(Holiday::new(
681            "IndependĂȘncia do Brasil",
682            NaiveDate::from_ymd_opt(year, 9, 7).unwrap(),
683            0.02,
684        ));
685
686        // Nossa Senhora Aparecida - October 12
687        cal.add_holiday(Holiday::new(
688            "Nossa Senhora Aparecida",
689            NaiveDate::from_ymd_opt(year, 10, 12).unwrap(),
690            0.02,
691        ));
692
693        // Finados - November 2
694        cal.add_holiday(Holiday::new(
695            "Finados",
696            NaiveDate::from_ymd_opt(year, 11, 2).unwrap(),
697            0.02,
698        ));
699
700        // Proclamação da RepĂșblica - November 15
701        cal.add_holiday(Holiday::new(
702            "Proclamação da RepĂșblica",
703            NaiveDate::from_ymd_opt(year, 11, 15).unwrap(),
704            0.02,
705        ));
706
707        // Natal - December 25
708        cal.add_holiday(Holiday::new(
709            "Natal",
710            NaiveDate::from_ymd_opt(year, 12, 25).unwrap(),
711            0.02,
712        ));
713
714        cal
715    }
716
717    /// Mexican holidays (national holidays).
718    fn mx_holidays(year: i32) -> Self {
719        let mut cal = Self::new(Region::MX, year);
720
721        // Año Nuevo - January 1
722        cal.add_holiday(Holiday::new(
723            "Año Nuevo",
724            NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
725            0.02,
726        ));
727
728        // DĂ­a de la ConstituciĂłn - First Monday of February
729        let constitution = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 1);
730        cal.add_holiday(Holiday::new("DĂ­a de la ConstituciĂłn", constitution, 0.02));
731
732        // Natalicio de Benito JuĂĄrez - Third Monday of March
733        let juarez = Self::nth_weekday_of_month(year, 3, Weekday::Mon, 3);
734        cal.add_holiday(Holiday::new("Natalicio de Benito JuĂĄrez", juarez, 0.02));
735
736        // Semana Santa - Holy Thursday and Good Friday
737        let easter = Self::easter_date(year);
738        cal.add_holiday(Holiday::new(
739            "Jueves Santo",
740            easter - Duration::days(3),
741            0.05,
742        ));
743        cal.add_holiday(Holiday::new(
744            "Viernes Santo",
745            easter - Duration::days(2),
746            0.02,
747        ));
748
749        // DĂ­a del Trabajo - May 1
750        cal.add_holiday(Holiday::new(
751            "DĂ­a del Trabajo",
752            NaiveDate::from_ymd_opt(year, 5, 1).unwrap(),
753            0.02,
754        ));
755
756        // DĂ­a de la Independencia - September 16
757        cal.add_holiday(Holiday::new(
758            "DĂ­a de la Independencia",
759            NaiveDate::from_ymd_opt(year, 9, 16).unwrap(),
760            0.02,
761        ));
762
763        // DĂ­a de la RevoluciĂłn - Third Monday of November
764        let revolution = Self::nth_weekday_of_month(year, 11, Weekday::Mon, 3);
765        cal.add_holiday(Holiday::new("DĂ­a de la RevoluciĂłn", revolution, 0.02));
766
767        // DĂ­a de Muertos - November 1-2 (not official but widely observed)
768        cal.add_holiday(Holiday::new(
769            "DĂ­a de Muertos",
770            NaiveDate::from_ymd_opt(year, 11, 1).unwrap(),
771            0.1,
772        ));
773        cal.add_holiday(Holiday::new(
774            "DĂ­a de Muertos",
775            NaiveDate::from_ymd_opt(year, 11, 2).unwrap(),
776            0.1,
777        ));
778
779        // Navidad - December 25
780        cal.add_holiday(Holiday::new(
781            "Navidad",
782            NaiveDate::from_ymd_opt(year, 12, 25).unwrap(),
783            0.02,
784        ));
785
786        cal
787    }
788
789    /// Australian holidays (national holidays).
790    fn au_holidays(year: i32) -> Self {
791        let mut cal = Self::new(Region::AU, year);
792
793        // New Year's Day - January 1
794        let new_years = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
795        cal.add_holiday(Holiday::new(
796            "New Year's Day",
797            Self::observe_weekend(new_years),
798            0.02,
799        ));
800
801        // Australia Day - January 26 (observed)
802        let australia_day = NaiveDate::from_ymd_opt(year, 1, 26).unwrap();
803        cal.add_holiday(Holiday::new(
804            "Australia Day",
805            Self::observe_weekend(australia_day),
806            0.02,
807        ));
808
809        // Good Friday
810        let easter = Self::easter_date(year);
811        cal.add_holiday(Holiday::new(
812            "Good Friday",
813            easter - Duration::days(2),
814            0.02,
815        ));
816
817        // Easter Saturday
818        cal.add_holiday(Holiday::new(
819            "Easter Saturday",
820            easter - Duration::days(1),
821            0.02,
822        ));
823
824        // Easter Monday
825        cal.add_holiday(Holiday::new(
826            "Easter Monday",
827            easter + Duration::days(1),
828            0.02,
829        ));
830
831        // ANZAC Day - April 25
832        let anzac = NaiveDate::from_ymd_opt(year, 4, 25).unwrap();
833        cal.add_holiday(Holiday::new("ANZAC Day", anzac, 0.02));
834
835        // Queen's Birthday - Second Monday of June (varies by state, using NSW)
836        let queens_birthday = Self::nth_weekday_of_month(year, 6, Weekday::Mon, 2);
837        cal.add_holiday(Holiday::new("Queen's Birthday", queens_birthday, 0.02));
838
839        // Christmas Day
840        let christmas = NaiveDate::from_ymd_opt(year, 12, 25).unwrap();
841        cal.add_holiday(Holiday::new(
842            "Christmas Day",
843            Self::observe_weekend(christmas),
844            0.02,
845        ));
846
847        // Boxing Day - December 26
848        let boxing = NaiveDate::from_ymd_opt(year, 12, 26).unwrap();
849        cal.add_holiday(Holiday::new(
850            "Boxing Day",
851            Self::observe_weekend(boxing),
852            0.02,
853        ));
854
855        cal
856    }
857
858    /// Singaporean holidays (national holidays).
859    fn sg_holidays(year: i32) -> Self {
860        let mut cal = Self::new(Region::SG, year);
861
862        // New Year's Day - January 1
863        cal.add_holiday(Holiday::new(
864            "New Year's Day",
865            NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
866            0.02,
867        ));
868
869        // Chinese New Year (2 days) - approximate
870        let cny = Self::approximate_chinese_new_year(year);
871        cal.add_holiday(Holiday::new("Chinese New Year", cny, 0.02));
872        cal.add_holiday(Holiday::new(
873            "Chinese New Year (Day 2)",
874            cny + Duration::days(1),
875            0.02,
876        ));
877
878        // Good Friday
879        let easter = Self::easter_date(year);
880        cal.add_holiday(Holiday::new(
881            "Good Friday",
882            easter - Duration::days(2),
883            0.02,
884        ));
885
886        // Labour Day - May 1
887        cal.add_holiday(Holiday::new(
888            "Labour Day",
889            NaiveDate::from_ymd_opt(year, 5, 1).unwrap(),
890            0.02,
891        ));
892
893        // Vesak Day - approximate (full moon in May)
894        let vesak = Self::approximate_vesak(year);
895        cal.add_holiday(Holiday::new("Vesak Day", vesak, 0.02));
896
897        // Hari Raya Puasa - approximate (end of Ramadan)
898        let hari_raya_puasa = Self::approximate_hari_raya_puasa(year);
899        cal.add_holiday(Holiday::new("Hari Raya Puasa", hari_raya_puasa, 0.02));
900
901        // Hari Raya Haji - approximate (Festival of Sacrifice)
902        let hari_raya_haji = Self::approximate_hari_raya_haji(year);
903        cal.add_holiday(Holiday::new("Hari Raya Haji", hari_raya_haji, 0.02));
904
905        // National Day - August 9
906        cal.add_holiday(Holiday::new(
907            "National Day",
908            NaiveDate::from_ymd_opt(year, 8, 9).unwrap(),
909            0.02,
910        ));
911
912        // Deepavali - approximate (October/November)
913        let deepavali = Self::approximate_deepavali(year);
914        cal.add_holiday(Holiday::new("Deepavali", deepavali, 0.02));
915
916        // Christmas Day
917        cal.add_holiday(Holiday::new(
918            "Christmas Day",
919            NaiveDate::from_ymd_opt(year, 12, 25).unwrap(),
920            0.02,
921        ));
922
923        cal
924    }
925
926    /// South Korean holidays (national holidays).
927    fn kr_holidays(year: i32) -> Self {
928        let mut cal = Self::new(Region::KR, year);
929
930        // New Year's Day - January 1
931        cal.add_holiday(Holiday::new(
932            "Sinjeong",
933            NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
934            0.02,
935        ));
936
937        // Seollal (Korean New Year) - 3 days around lunar new year
938        let seollal = Self::approximate_korean_new_year(year);
939        cal.add_holiday(Holiday::new(
940            "Seollal (Eve)",
941            seollal - Duration::days(1),
942            0.02,
943        ));
944        cal.add_holiday(Holiday::new("Seollal", seollal, 0.02));
945        cal.add_holiday(Holiday::new(
946            "Seollal (Day 2)",
947            seollal + Duration::days(1),
948            0.02,
949        ));
950
951        // Independence Movement Day - March 1
952        cal.add_holiday(Holiday::new(
953            "Samiljeol",
954            NaiveDate::from_ymd_opt(year, 3, 1).unwrap(),
955            0.02,
956        ));
957
958        // Children's Day - May 5
959        cal.add_holiday(Holiday::new(
960            "Eorininal",
961            NaiveDate::from_ymd_opt(year, 5, 5).unwrap(),
962            0.02,
963        ));
964
965        // Buddha's Birthday - approximate (8th day of 4th lunar month)
966        let buddha_birthday = Self::approximate_korean_buddha_birthday(year);
967        cal.add_holiday(Holiday::new("Seokgatansinil", buddha_birthday, 0.02));
968
969        // Memorial Day - June 6
970        cal.add_holiday(Holiday::new(
971            "Hyeonchungil",
972            NaiveDate::from_ymd_opt(year, 6, 6).unwrap(),
973            0.02,
974        ));
975
976        // Liberation Day - August 15
977        cal.add_holiday(Holiday::new(
978            "Gwangbokjeol",
979            NaiveDate::from_ymd_opt(year, 8, 15).unwrap(),
980            0.02,
981        ));
982
983        // Chuseok (Korean Thanksgiving) - 3 days around harvest moon
984        let chuseok = Self::approximate_chuseok(year);
985        cal.add_holiday(Holiday::new(
986            "Chuseok (Eve)",
987            chuseok - Duration::days(1),
988            0.02,
989        ));
990        cal.add_holiday(Holiday::new("Chuseok", chuseok, 0.02));
991        cal.add_holiday(Holiday::new(
992            "Chuseok (Day 2)",
993            chuseok + Duration::days(1),
994            0.02,
995        ));
996
997        // National Foundation Day - October 3
998        cal.add_holiday(Holiday::new(
999            "Gaecheonjeol",
1000            NaiveDate::from_ymd_opt(year, 10, 3).unwrap(),
1001            0.02,
1002        ));
1003
1004        // Hangul Day - October 9
1005        cal.add_holiday(Holiday::new(
1006            "Hangullal",
1007            NaiveDate::from_ymd_opt(year, 10, 9).unwrap(),
1008            0.02,
1009        ));
1010
1011        // Christmas - December 25
1012        cal.add_holiday(Holiday::new(
1013            "Seongtanjeol",
1014            NaiveDate::from_ymd_opt(year, 12, 25).unwrap(),
1015            0.02,
1016        ));
1017
1018        cal
1019    }
1020
1021    /// Calculate Easter date using the anonymous Gregorian algorithm.
1022    fn easter_date(year: i32) -> NaiveDate {
1023        let a = year % 19;
1024        let b = year / 100;
1025        let c = year % 100;
1026        let d = b / 4;
1027        let e = b % 4;
1028        let f = (b + 8) / 25;
1029        let g = (b - f + 1) / 3;
1030        let h = (19 * a + b - d - g + 15) % 30;
1031        let i = c / 4;
1032        let k = c % 4;
1033        let l = (32 + 2 * e + 2 * i - h - k) % 7;
1034        let m = (a + 11 * h + 22 * l) / 451;
1035        let month = (h + l - 7 * m + 114) / 31;
1036        let day = ((h + l - 7 * m + 114) % 31) + 1;
1037
1038        NaiveDate::from_ymd_opt(year, month as u32, day as u32).unwrap()
1039    }
1040
1041    /// Get nth weekday of a month (e.g., 3rd Monday of January).
1042    fn nth_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u32) -> NaiveDate {
1043        let first = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
1044        let first_weekday = first.weekday();
1045
1046        let days_until = (weekday.num_days_from_monday() as i64
1047            - first_weekday.num_days_from_monday() as i64
1048            + 7)
1049            % 7;
1050
1051        first + Duration::days(days_until + (n - 1) as i64 * 7)
1052    }
1053
1054    /// Get last weekday of a month (e.g., last Monday of May).
1055    fn last_weekday_of_month(year: i32, month: u32, weekday: Weekday) -> NaiveDate {
1056        let last = if month == 12 {
1057            NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap() - Duration::days(1)
1058        } else {
1059            NaiveDate::from_ymd_opt(year, month + 1, 1).unwrap() - Duration::days(1)
1060        };
1061
1062        let last_weekday = last.weekday();
1063        let days_back = (last_weekday.num_days_from_monday() as i64
1064            - weekday.num_days_from_monday() as i64
1065            + 7)
1066            % 7;
1067
1068        last - Duration::days(days_back)
1069    }
1070
1071    /// Observe weekend holidays on nearest weekday.
1072    fn observe_weekend(date: NaiveDate) -> NaiveDate {
1073        match date.weekday() {
1074            Weekday::Sat => date - Duration::days(1), // Friday
1075            Weekday::Sun => date + Duration::days(1), // Monday
1076            _ => date,
1077        }
1078    }
1079
1080    /// Approximate Chinese New Year date (simplified calculation).
1081    fn approximate_chinese_new_year(year: i32) -> NaiveDate {
1082        // Chinese New Year falls between Jan 21 and Feb 20
1083        // This is a simplified approximation
1084        let base_year = 2000;
1085        let cny_2000 = NaiveDate::from_ymd_opt(2000, 2, 5).unwrap();
1086
1087        let years_diff = year - base_year;
1088        let lunar_cycle = 29.5306; // days per lunar month
1089        let days_offset = (years_diff as f64 * 12.0 * lunar_cycle) % 365.25;
1090
1091        let mut result = cny_2000 + Duration::days(days_offset as i64);
1092
1093        // Ensure it falls in Jan-Feb range
1094        while result.month() > 2 || (result.month() == 2 && result.day() > 20) {
1095            result -= Duration::days(29);
1096        }
1097        while result.month() < 1 || (result.month() == 1 && result.day() < 21) {
1098            result += Duration::days(29);
1099        }
1100
1101        // Adjust year if needed
1102        if result.year() != year {
1103            result = NaiveDate::from_ymd_opt(year, result.month(), result.day().min(28))
1104                .unwrap_or_else(|| NaiveDate::from_ymd_opt(year, result.month(), 28).unwrap());
1105        }
1106
1107        result
1108    }
1109
1110    /// Approximate Diwali date (simplified calculation).
1111    fn approximate_diwali(year: i32) -> NaiveDate {
1112        // Diwali typically falls in October-November
1113        // This is a simplified approximation
1114        match year % 4 {
1115            0 => NaiveDate::from_ymd_opt(year, 11, 1).unwrap(),
1116            1 => NaiveDate::from_ymd_opt(year, 10, 24).unwrap(),
1117            2 => NaiveDate::from_ymd_opt(year, 11, 12).unwrap(),
1118            _ => NaiveDate::from_ymd_opt(year, 11, 4).unwrap(),
1119        }
1120    }
1121
1122    /// Approximate Vesak Day (Buddha's Birthday in Theravada tradition).
1123    /// Falls on the full moon of the 4th lunar month (usually May).
1124    fn approximate_vesak(year: i32) -> NaiveDate {
1125        // Vesak is typically in May
1126        // Using approximate lunar cycle calculation
1127        let base = match year % 19 {
1128            0 => 18,
1129            1 => 7,
1130            2 => 26,
1131            3 => 15,
1132            4 => 5,
1133            5 => 24,
1134            6 => 13,
1135            7 => 2,
1136            8 => 22,
1137            9 => 11,
1138            10 => 30,
1139            11 => 19,
1140            12 => 8,
1141            13 => 27,
1142            14 => 17,
1143            15 => 6,
1144            16 => 25,
1145            17 => 14,
1146            _ => 3,
1147        };
1148        let month = if base > 20 { 4 } else { 5 };
1149        let day = if base > 20 { base - 10 } else { base };
1150        NaiveDate::from_ymd_opt(year, month, day.clamp(1, 28) as u32).unwrap()
1151    }
1152
1153    /// Approximate Hari Raya Puasa (Eid al-Fitr).
1154    /// Based on Islamic lunar calendar (moves ~11 days earlier each year).
1155    fn approximate_hari_raya_puasa(year: i32) -> NaiveDate {
1156        // Islamic calendar moves about 11 days earlier each year
1157        // Base: 2024 Eid al-Fitr was approximately April 10
1158        let base_year = 2024;
1159        let base_date = NaiveDate::from_ymd_opt(2024, 4, 10).unwrap();
1160        let years_diff = year - base_year;
1161        let days_shift = (years_diff as f64 * -10.63) as i64;
1162        let mut result = base_date + Duration::days(days_shift);
1163
1164        // Wrap around to stay in valid range
1165        while result.year() != year {
1166            if result.year() > year {
1167                result -= Duration::days(354); // Islamic lunar year
1168            } else {
1169                result += Duration::days(354);
1170            }
1171        }
1172        result
1173    }
1174
1175    /// Approximate Hari Raya Haji (Eid al-Adha).
1176    /// Approximately 70 days after Hari Raya Puasa.
1177    fn approximate_hari_raya_haji(year: i32) -> NaiveDate {
1178        Self::approximate_hari_raya_puasa(year) + Duration::days(70)
1179    }
1180
1181    /// Approximate Deepavali date (same as Diwali).
1182    fn approximate_deepavali(year: i32) -> NaiveDate {
1183        Self::approximate_diwali(year)
1184    }
1185
1186    /// Approximate Korean New Year (Seollal).
1187    /// Similar to Chinese New Year but may differ by a day.
1188    fn approximate_korean_new_year(year: i32) -> NaiveDate {
1189        Self::approximate_chinese_new_year(year)
1190    }
1191
1192    /// Approximate Korean Buddha's Birthday.
1193    /// 8th day of the 4th lunar month.
1194    fn approximate_korean_buddha_birthday(year: i32) -> NaiveDate {
1195        // Typically falls in late April to late May
1196        match year % 19 {
1197            0 => NaiveDate::from_ymd_opt(year, 5, 15).unwrap(),
1198            1 => NaiveDate::from_ymd_opt(year, 5, 4).unwrap(),
1199            2 => NaiveDate::from_ymd_opt(year, 5, 23).unwrap(),
1200            3 => NaiveDate::from_ymd_opt(year, 5, 12).unwrap(),
1201            4 => NaiveDate::from_ymd_opt(year, 5, 1).unwrap(),
1202            5 => NaiveDate::from_ymd_opt(year, 5, 20).unwrap(),
1203            6 => NaiveDate::from_ymd_opt(year, 5, 10).unwrap(),
1204            7 => NaiveDate::from_ymd_opt(year, 4, 29).unwrap(),
1205            8 => NaiveDate::from_ymd_opt(year, 5, 18).unwrap(),
1206            9 => NaiveDate::from_ymd_opt(year, 5, 7).unwrap(),
1207            10 => NaiveDate::from_ymd_opt(year, 5, 26).unwrap(),
1208            11 => NaiveDate::from_ymd_opt(year, 5, 15).unwrap(),
1209            12 => NaiveDate::from_ymd_opt(year, 5, 4).unwrap(),
1210            13 => NaiveDate::from_ymd_opt(year, 5, 24).unwrap(),
1211            14 => NaiveDate::from_ymd_opt(year, 5, 13).unwrap(),
1212            15 => NaiveDate::from_ymd_opt(year, 5, 2).unwrap(),
1213            16 => NaiveDate::from_ymd_opt(year, 5, 21).unwrap(),
1214            17 => NaiveDate::from_ymd_opt(year, 5, 10).unwrap(),
1215            _ => NaiveDate::from_ymd_opt(year, 4, 30).unwrap(),
1216        }
1217    }
1218
1219    /// Approximate Chuseok (Korean Thanksgiving).
1220    /// 15th day of the 8th lunar month (harvest moon).
1221    fn approximate_chuseok(year: i32) -> NaiveDate {
1222        // Chuseok typically falls in September or early October
1223        match year % 19 {
1224            0 => NaiveDate::from_ymd_opt(year, 9, 17).unwrap(),
1225            1 => NaiveDate::from_ymd_opt(year, 10, 6).unwrap(),
1226            2 => NaiveDate::from_ymd_opt(year, 9, 25).unwrap(),
1227            3 => NaiveDate::from_ymd_opt(year, 9, 14).unwrap(),
1228            4 => NaiveDate::from_ymd_opt(year, 10, 3).unwrap(),
1229            5 => NaiveDate::from_ymd_opt(year, 9, 22).unwrap(),
1230            6 => NaiveDate::from_ymd_opt(year, 9, 11).unwrap(),
1231            7 => NaiveDate::from_ymd_opt(year, 9, 30).unwrap(),
1232            8 => NaiveDate::from_ymd_opt(year, 9, 19).unwrap(),
1233            9 => NaiveDate::from_ymd_opt(year, 10, 9).unwrap(),
1234            10 => NaiveDate::from_ymd_opt(year, 9, 28).unwrap(),
1235            11 => NaiveDate::from_ymd_opt(year, 9, 17).unwrap(),
1236            12 => NaiveDate::from_ymd_opt(year, 10, 6).unwrap(),
1237            13 => NaiveDate::from_ymd_opt(year, 9, 25).unwrap(),
1238            14 => NaiveDate::from_ymd_opt(year, 9, 14).unwrap(),
1239            15 => NaiveDate::from_ymd_opt(year, 10, 4).unwrap(),
1240            16 => NaiveDate::from_ymd_opt(year, 9, 22).unwrap(),
1241            17 => NaiveDate::from_ymd_opt(year, 9, 12).unwrap(),
1242            _ => NaiveDate::from_ymd_opt(year, 10, 1).unwrap(),
1243        }
1244    }
1245}
1246
1247/// Custom holiday configuration for YAML/JSON input.
1248#[derive(Debug, Clone, Serialize, Deserialize)]
1249pub struct CustomHolidayConfig {
1250    /// Holiday name.
1251    pub name: String,
1252    /// Month (1-12).
1253    pub month: u8,
1254    /// Day of month.
1255    pub day: u8,
1256    /// Activity multiplier (optional, defaults to 0.05).
1257    #[serde(default = "default_holiday_multiplier")]
1258    pub activity_multiplier: f64,
1259}
1260
1261fn default_holiday_multiplier() -> f64 {
1262    0.05
1263}
1264
1265impl CustomHolidayConfig {
1266    /// Convert to a Holiday for a specific year.
1267    pub fn to_holiday(&self, year: i32) -> Holiday {
1268        Holiday::new(
1269            &self.name,
1270            NaiveDate::from_ymd_opt(year, self.month as u32, self.day as u32).unwrap(),
1271            self.activity_multiplier,
1272        )
1273    }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279
1280    #[test]
1281    fn test_us_holidays() {
1282        let cal = HolidayCalendar::for_region(Region::US, 2024);
1283
1284        // Check some specific holidays exist
1285        let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1286        assert!(cal.is_holiday(christmas));
1287
1288        // Independence Day (observed on Friday since July 4 is Thursday in 2024)
1289        let independence = NaiveDate::from_ymd_opt(2024, 7, 4).unwrap();
1290        assert!(cal.is_holiday(independence));
1291    }
1292
1293    #[test]
1294    fn test_german_holidays() {
1295        let cal = HolidayCalendar::for_region(Region::DE, 2024);
1296
1297        // Tag der Deutschen Einheit - October 3
1298        let unity = NaiveDate::from_ymd_opt(2024, 10, 3).unwrap();
1299        assert!(cal.is_holiday(unity));
1300    }
1301
1302    #[test]
1303    fn test_easter_calculation() {
1304        // Known Easter dates
1305        assert_eq!(
1306            HolidayCalendar::easter_date(2024),
1307            NaiveDate::from_ymd_opt(2024, 3, 31).unwrap()
1308        );
1309        assert_eq!(
1310            HolidayCalendar::easter_date(2025),
1311            NaiveDate::from_ymd_opt(2025, 4, 20).unwrap()
1312        );
1313    }
1314
1315    #[test]
1316    fn test_nth_weekday() {
1317        // 3rd Monday of January 2024
1318        let mlk = HolidayCalendar::nth_weekday_of_month(2024, 1, Weekday::Mon, 3);
1319        assert_eq!(mlk, NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1320
1321        // 4th Thursday of November 2024 (Thanksgiving)
1322        let thanksgiving = HolidayCalendar::nth_weekday_of_month(2024, 11, Weekday::Thu, 4);
1323        assert_eq!(thanksgiving, NaiveDate::from_ymd_opt(2024, 11, 28).unwrap());
1324    }
1325
1326    #[test]
1327    fn test_last_weekday() {
1328        // Last Monday of May 2024 (Memorial Day)
1329        let memorial = HolidayCalendar::last_weekday_of_month(2024, 5, Weekday::Mon);
1330        assert_eq!(memorial, NaiveDate::from_ymd_opt(2024, 5, 27).unwrap());
1331    }
1332
1333    #[test]
1334    fn test_activity_multiplier() {
1335        let cal = HolidayCalendar::for_region(Region::US, 2024);
1336
1337        // Holiday should have low multiplier
1338        let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1339        assert!(cal.get_multiplier(christmas) < 0.1);
1340
1341        // Regular day should be 1.0
1342        let regular = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
1343        assert!((cal.get_multiplier(regular) - 1.0).abs() < 0.01);
1344    }
1345
1346    #[test]
1347    fn test_all_regions_have_holidays() {
1348        let regions = [
1349            Region::US,
1350            Region::DE,
1351            Region::GB,
1352            Region::CN,
1353            Region::JP,
1354            Region::IN,
1355            Region::BR,
1356            Region::MX,
1357            Region::AU,
1358            Region::SG,
1359            Region::KR,
1360        ];
1361
1362        for region in regions {
1363            let cal = HolidayCalendar::for_region(region, 2024);
1364            assert!(
1365                !cal.holidays.is_empty(),
1366                "Region {:?} should have holidays",
1367                region
1368            );
1369        }
1370    }
1371
1372    #[test]
1373    fn test_brazilian_holidays() {
1374        let cal = HolidayCalendar::for_region(Region::BR, 2024);
1375
1376        // IndependĂȘncia do Brasil - September 7
1377        let independence = NaiveDate::from_ymd_opt(2024, 9, 7).unwrap();
1378        assert!(cal.is_holiday(independence));
1379
1380        // Tiradentes - April 21
1381        let tiradentes = NaiveDate::from_ymd_opt(2024, 4, 21).unwrap();
1382        assert!(cal.is_holiday(tiradentes));
1383    }
1384
1385    #[test]
1386    fn test_mexican_holidays() {
1387        let cal = HolidayCalendar::for_region(Region::MX, 2024);
1388
1389        // DĂ­a de la Independencia - September 16
1390        let independence = NaiveDate::from_ymd_opt(2024, 9, 16).unwrap();
1391        assert!(cal.is_holiday(independence));
1392    }
1393
1394    #[test]
1395    fn test_australian_holidays() {
1396        let cal = HolidayCalendar::for_region(Region::AU, 2024);
1397
1398        // ANZAC Day - April 25
1399        let anzac = NaiveDate::from_ymd_opt(2024, 4, 25).unwrap();
1400        assert!(cal.is_holiday(anzac));
1401
1402        // Australia Day - January 26
1403        let australia_day = NaiveDate::from_ymd_opt(2024, 1, 26).unwrap();
1404        assert!(cal.is_holiday(australia_day));
1405    }
1406
1407    #[test]
1408    fn test_singapore_holidays() {
1409        let cal = HolidayCalendar::for_region(Region::SG, 2024);
1410
1411        // National Day - August 9
1412        let national = NaiveDate::from_ymd_opt(2024, 8, 9).unwrap();
1413        assert!(cal.is_holiday(national));
1414    }
1415
1416    #[test]
1417    fn test_korean_holidays() {
1418        let cal = HolidayCalendar::for_region(Region::KR, 2024);
1419
1420        // Liberation Day - August 15
1421        let liberation = NaiveDate::from_ymd_opt(2024, 8, 15).unwrap();
1422        assert!(cal.is_holiday(liberation));
1423
1424        // Hangul Day - October 9
1425        let hangul = NaiveDate::from_ymd_opt(2024, 10, 9).unwrap();
1426        assert!(cal.is_holiday(hangul));
1427    }
1428
1429    #[test]
1430    fn test_chinese_holidays() {
1431        let cal = HolidayCalendar::for_region(Region::CN, 2024);
1432
1433        // National Day - October 1
1434        let national = NaiveDate::from_ymd_opt(2024, 10, 1).unwrap();
1435        assert!(cal.is_holiday(national));
1436    }
1437
1438    #[test]
1439    fn test_japanese_golden_week() {
1440        let cal = HolidayCalendar::for_region(Region::JP, 2024);
1441
1442        // Check Golden Week holidays
1443        let kodomo = NaiveDate::from_ymd_opt(2024, 5, 5).unwrap();
1444        assert!(cal.is_holiday(kodomo));
1445    }
1446}