1use chrono::{Datelike, Duration, NaiveDate, Weekday};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "UPPERCASE")]
12pub enum Region {
13 US,
15 DE,
17 GB,
19 CN,
21 JP,
23 IN,
25 BR,
27 MX,
29 AU,
31 SG,
33 KR,
35 FR,
37 IT,
39 ES,
41 CA,
43}
44
45impl std::fmt::Display for Region {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Region::US => write!(f, "United States"),
49 Region::DE => write!(f, "Germany"),
50 Region::GB => write!(f, "United Kingdom"),
51 Region::CN => write!(f, "China"),
52 Region::JP => write!(f, "Japan"),
53 Region::IN => write!(f, "India"),
54 Region::BR => write!(f, "Brazil"),
55 Region::MX => write!(f, "Mexico"),
56 Region::AU => write!(f, "Australia"),
57 Region::SG => write!(f, "Singapore"),
58 Region::KR => write!(f, "South Korea"),
59 Region::FR => write!(f, "France"),
60 Region::IT => write!(f, "Italy"),
61 Region::ES => write!(f, "Spain"),
62 Region::CA => write!(f, "Canada"),
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct Holiday {
70 pub name: String,
72 pub date: NaiveDate,
74 pub activity_multiplier: f64,
76 pub is_bank_holiday: bool,
78}
79
80impl Holiday {
81 pub fn new(name: impl Into<String>, date: NaiveDate, multiplier: f64) -> Self {
83 Self {
84 name: name.into(),
85 date,
86 activity_multiplier: multiplier,
87 is_bank_holiday: true,
88 }
89 }
90
91 pub fn with_bank_holiday(mut self, is_bank_holiday: bool) -> Self {
93 self.is_bank_holiday = is_bank_holiday;
94 self
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct HolidayCalendar {
101 pub region: Region,
103 pub year: i32,
105 pub holidays: Vec<Holiday>,
107}
108
109impl HolidayCalendar {
110 pub fn new(region: Region, year: i32) -> Self {
112 Self {
113 region,
114 year,
115 holidays: Vec::new(),
116 }
117 }
118
119 pub fn for_region(region: Region, year: i32) -> Self {
121 match region {
122 Region::US => Self::us_holidays(year),
123 Region::DE => Self::de_holidays(year),
124 Region::GB => Self::gb_holidays(year),
125 Region::CN => Self::cn_holidays(year),
126 Region::JP => Self::jp_holidays(year),
127 Region::IN => Self::in_holidays(year),
128 Region::BR => Self::br_holidays(year),
129 Region::MX => Self::mx_holidays(year),
130 Region::AU => Self::au_holidays(year),
131 Region::SG => Self::sg_holidays(year),
132 Region::KR => Self::kr_holidays(year),
133 Region::FR => Self::fr_holidays(year),
134 Region::IT => Self::it_holidays(year),
135 Region::ES => Self::es_holidays(year),
136 Region::CA => Self::ca_holidays(year),
137 }
138 }
139
140 pub fn is_holiday(&self, date: NaiveDate) -> bool {
142 self.holidays.iter().any(|h| h.date == date)
143 }
144
145 pub fn get_multiplier(&self, date: NaiveDate) -> f64 {
147 self.holidays
148 .iter()
149 .find(|h| h.date == date)
150 .map(|h| h.activity_multiplier)
151 .unwrap_or(1.0)
152 }
153
154 pub fn get_holidays(&self, date: NaiveDate) -> Vec<&Holiday> {
156 self.holidays.iter().filter(|h| h.date == date).collect()
157 }
158
159 pub fn add_holiday(&mut self, holiday: Holiday) {
161 self.holidays.push(holiday);
162 }
163
164 pub fn all_dates(&self) -> Vec<NaiveDate> {
166 self.holidays.iter().map(|h| h.date).collect()
167 }
168
169 pub fn from_country_pack(pack: &crate::country::schema::CountryPack, year: i32) -> Self {
176 let region = match pack.country_code.as_str() {
178 "US" => Region::US,
179 "DE" => Region::DE,
180 "GB" => Region::GB,
181 "CN" => Region::CN,
182 "JP" => Region::JP,
183 "IN" => Region::IN,
184 "BR" => Region::BR,
185 "MX" => Region::MX,
186 "AU" => Region::AU,
187 "SG" => Region::SG,
188 "KR" => Region::KR,
189 "FR" => Region::FR,
190 "IT" => Region::IT,
191 "ES" => Region::ES,
192 "CA" => Region::CA,
193 _ => Region::US,
194 };
195
196 let mut cal = Self::new(region, year);
197 let holidays = &pack.holidays;
198
199 for h in &holidays.fixed {
201 if let Some(date) = NaiveDate::from_ymd_opt(year, h.month, h.day) {
202 let date = if h.observe_weekend_rule {
203 Self::observe_weekend(date)
204 } else {
205 date
206 };
207 cal.add_holiday(Holiday::new(&h.name, date, h.activity_multiplier));
208 }
209 }
210
211 if let Some(easter) = crate::country::easter::compute_easter(year) {
213 for h in &holidays.easter_relative {
214 let date = easter + Duration::days(h.offset_days as i64);
215 cal.add_holiday(Holiday::new(&h.name, date, h.activity_multiplier));
216 }
217 }
218
219 for h in &holidays.nth_weekday {
221 if let Some(weekday) = Self::parse_weekday(&h.weekday) {
222 let date = Self::nth_weekday_of_month(year, h.month, weekday, h.occurrence);
223 let date = date + Duration::days(h.offset_days as i64);
224 cal.add_holiday(Holiday::new(&h.name, date, h.activity_multiplier));
225 }
226 }
227
228 for h in &holidays.last_weekday {
230 if let Some(weekday) = Self::parse_weekday(&h.weekday) {
231 let date = Self::last_weekday_of_month(year, h.month, weekday);
232 cal.add_holiday(Holiday::new(&h.name, date, h.activity_multiplier));
233 }
234 }
235
236 for h in &holidays.lunar {
238 if let Some(dates) =
239 crate::country::lunar::resolve_lunar_holiday(&h.algorithm, year, h.duration_days)
240 {
241 for date in dates {
242 cal.add_holiday(Holiday::new(&h.name, date, h.activity_multiplier));
243 }
244 }
245 }
246
247 cal
248 }
249
250 fn parse_weekday(s: &str) -> Option<Weekday> {
252 match s.to_lowercase().as_str() {
253 "monday" | "mon" => Some(Weekday::Mon),
254 "tuesday" | "tue" => Some(Weekday::Tue),
255 "wednesday" | "wed" => Some(Weekday::Wed),
256 "thursday" | "thu" => Some(Weekday::Thu),
257 "friday" | "fri" => Some(Weekday::Fri),
258 "saturday" | "sat" => Some(Weekday::Sat),
259 "sunday" | "sun" => Some(Weekday::Sun),
260 _ => None,
261 }
262 }
263
264 fn us_holidays(year: i32) -> Self {
266 let mut cal = Self::new(Region::US, year);
267
268 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
270 cal.add_holiday(Holiday::new(
271 "New Year's Day",
272 Self::observe_weekend(new_years),
273 0.02,
274 ));
275
276 let mlk = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 3);
278 cal.add_holiday(Holiday::new("Martin Luther King Jr. Day", mlk, 0.1));
279
280 let presidents = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 3);
282 cal.add_holiday(Holiday::new("Presidents' Day", presidents, 0.1));
283
284 let memorial = Self::last_weekday_of_month(year, 5, Weekday::Mon);
286 cal.add_holiday(Holiday::new("Memorial Day", memorial, 0.05));
287
288 let juneteenth = NaiveDate::from_ymd_opt(year, 6, 19).expect("valid date components");
290 cal.add_holiday(Holiday::new(
291 "Juneteenth",
292 Self::observe_weekend(juneteenth),
293 0.1,
294 ));
295
296 let independence = NaiveDate::from_ymd_opt(year, 7, 4).expect("valid date components");
298 cal.add_holiday(Holiday::new(
299 "Independence Day",
300 Self::observe_weekend(independence),
301 0.02,
302 ));
303
304 let labor = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 1);
306 cal.add_holiday(Holiday::new("Labor Day", labor, 0.05));
307
308 let columbus = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
310 cal.add_holiday(Holiday::new("Columbus Day", columbus, 0.2));
311
312 let veterans = NaiveDate::from_ymd_opt(year, 11, 11).expect("valid date components");
314 cal.add_holiday(Holiday::new(
315 "Veterans Day",
316 Self::observe_weekend(veterans),
317 0.1,
318 ));
319
320 let thanksgiving = Self::nth_weekday_of_month(year, 11, Weekday::Thu, 4);
322 cal.add_holiday(Holiday::new("Thanksgiving", thanksgiving, 0.02));
323
324 cal.add_holiday(Holiday::new(
326 "Day after Thanksgiving",
327 thanksgiving + Duration::days(1),
328 0.1,
329 ));
330
331 let christmas_eve = NaiveDate::from_ymd_opt(year, 12, 24).expect("valid date components");
333 cal.add_holiday(Holiday::new("Christmas Eve", christmas_eve, 0.1));
334
335 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
337 cal.add_holiday(Holiday::new(
338 "Christmas Day",
339 Self::observe_weekend(christmas),
340 0.02,
341 ));
342
343 let new_years_eve = NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components");
345 cal.add_holiday(Holiday::new("New Year's Eve", new_years_eve, 0.1));
346
347 cal
348 }
349
350 fn de_holidays(year: i32) -> Self {
352 let mut cal = Self::new(Region::DE, year);
353
354 cal.add_holiday(Holiday::new(
356 "Neujahr",
357 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
358 0.02,
359 ));
360
361 let easter = Self::easter_date(year);
363 cal.add_holiday(Holiday::new("Karfreitag", easter - Duration::days(2), 0.02));
364
365 cal.add_holiday(Holiday::new(
367 "Ostermontag",
368 easter + Duration::days(1),
369 0.02,
370 ));
371
372 cal.add_holiday(Holiday::new(
374 "Tag der Arbeit",
375 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
376 0.02,
377 ));
378
379 cal.add_holiday(Holiday::new(
381 "Christi Himmelfahrt",
382 easter + Duration::days(39),
383 0.02,
384 ));
385
386 cal.add_holiday(Holiday::new(
388 "Pfingstmontag",
389 easter + Duration::days(50),
390 0.02,
391 ));
392
393 cal.add_holiday(Holiday::new(
395 "Tag der Deutschen Einheit",
396 NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
397 0.02,
398 ));
399
400 cal.add_holiday(Holiday::new(
402 "1. Weihnachtstag",
403 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
404 0.02,
405 ));
406 cal.add_holiday(Holiday::new(
407 "2. Weihnachtstag",
408 NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components"),
409 0.02,
410 ));
411
412 cal.add_holiday(Holiday::new(
414 "Silvester",
415 NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components"),
416 0.1,
417 ));
418
419 cal
420 }
421
422 fn gb_holidays(year: i32) -> Self {
424 let mut cal = Self::new(Region::GB, year);
425
426 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
428 cal.add_holiday(Holiday::new(
429 "New Year's Day",
430 Self::observe_weekend(new_years),
431 0.02,
432 ));
433
434 let easter = Self::easter_date(year);
436 cal.add_holiday(Holiday::new(
437 "Good Friday",
438 easter - Duration::days(2),
439 0.02,
440 ));
441
442 cal.add_holiday(Holiday::new(
444 "Easter Monday",
445 easter + Duration::days(1),
446 0.02,
447 ));
448
449 let early_may = Self::nth_weekday_of_month(year, 5, Weekday::Mon, 1);
451 cal.add_holiday(Holiday::new("Early May Bank Holiday", early_may, 0.02));
452
453 let spring = Self::last_weekday_of_month(year, 5, Weekday::Mon);
455 cal.add_holiday(Holiday::new("Spring Bank Holiday", spring, 0.02));
456
457 let summer = Self::last_weekday_of_month(year, 8, Weekday::Mon);
459 cal.add_holiday(Holiday::new("Summer Bank Holiday", summer, 0.02));
460
461 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
463 cal.add_holiday(Holiday::new(
464 "Christmas Day",
465 Self::observe_weekend(christmas),
466 0.02,
467 ));
468
469 let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
471 cal.add_holiday(Holiday::new(
472 "Boxing Day",
473 Self::observe_weekend(boxing),
474 0.02,
475 ));
476
477 cal
478 }
479
480 fn cn_holidays(year: i32) -> Self {
482 let mut cal = Self::new(Region::CN, year);
483
484 cal.add_holiday(Holiday::new(
486 "New Year",
487 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
488 0.05,
489 ));
490
491 let cny = Self::approximate_chinese_new_year(year);
494 for i in 0..7 {
495 cal.add_holiday(Holiday::new(
496 if i == 0 {
497 "Spring Festival"
498 } else {
499 "Spring Festival Holiday"
500 },
501 cny + Duration::days(i),
502 0.02,
503 ));
504 }
505
506 cal.add_holiday(Holiday::new(
508 "Qingming Festival",
509 NaiveDate::from_ymd_opt(year, 4, 5).expect("valid date components"),
510 0.05,
511 ));
512
513 for i in 0..3 {
515 cal.add_holiday(Holiday::new(
516 if i == 0 {
517 "Labor Day"
518 } else {
519 "Labor Day Holiday"
520 },
521 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components")
522 + Duration::days(i),
523 0.05,
524 ));
525 }
526
527 cal.add_holiday(Holiday::new(
529 "Dragon Boat Festival",
530 NaiveDate::from_ymd_opt(year, 6, 10).expect("valid date components"),
531 0.05,
532 ));
533
534 cal.add_holiday(Holiday::new(
536 "Mid-Autumn Festival",
537 NaiveDate::from_ymd_opt(year, 9, 15).expect("valid date components"),
538 0.05,
539 ));
540
541 for i in 0..7 {
543 cal.add_holiday(Holiday::new(
544 if i == 0 {
545 "National Day"
546 } else {
547 "National Day Holiday"
548 },
549 NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components")
550 + Duration::days(i),
551 0.02,
552 ));
553 }
554
555 cal
556 }
557
558 fn jp_holidays(year: i32) -> Self {
560 let mut cal = Self::new(Region::JP, year);
561
562 cal.add_holiday(Holiday::new(
564 "Ganjitsu (New Year)",
565 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
566 0.02,
567 ));
568
569 cal.add_holiday(Holiday::new(
571 "New Year Holiday",
572 NaiveDate::from_ymd_opt(year, 1, 2).expect("valid date components"),
573 0.05,
574 ));
575 cal.add_holiday(Holiday::new(
576 "New Year Holiday",
577 NaiveDate::from_ymd_opt(year, 1, 3).expect("valid date components"),
578 0.05,
579 ));
580
581 let seijin = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 2);
583 cal.add_holiday(Holiday::new("Seijin no Hi", seijin, 0.05));
584
585 cal.add_holiday(Holiday::new(
587 "Kenkoku Kinen no Hi",
588 NaiveDate::from_ymd_opt(year, 2, 11).expect("valid date components"),
589 0.02,
590 ));
591
592 cal.add_holiday(Holiday::new(
594 "Tenno Tanjobi",
595 NaiveDate::from_ymd_opt(year, 2, 23).expect("valid date components"),
596 0.02,
597 ));
598
599 cal.add_holiday(Holiday::new(
601 "Shunbun no Hi",
602 NaiveDate::from_ymd_opt(year, 3, 20).expect("valid date components"),
603 0.02,
604 ));
605
606 cal.add_holiday(Holiday::new(
608 "Showa no Hi",
609 NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
610 0.02,
611 ));
612
613 cal.add_holiday(Holiday::new(
615 "Kenpo Kinenbi",
616 NaiveDate::from_ymd_opt(year, 5, 3).expect("valid date components"),
617 0.02,
618 ));
619 cal.add_holiday(Holiday::new(
620 "Midori no Hi",
621 NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
622 0.02,
623 ));
624 cal.add_holiday(Holiday::new(
625 "Kodomo no Hi",
626 NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
627 0.02,
628 ));
629
630 let umi = Self::nth_weekday_of_month(year, 7, Weekday::Mon, 3);
632 cal.add_holiday(Holiday::new("Umi no Hi", umi, 0.05));
633
634 cal.add_holiday(Holiday::new(
636 "Yama no Hi",
637 NaiveDate::from_ymd_opt(year, 8, 11).expect("valid date components"),
638 0.05,
639 ));
640
641 let keiro = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 3);
643 cal.add_holiday(Holiday::new("Keiro no Hi", keiro, 0.05));
644
645 cal.add_holiday(Holiday::new(
647 "Shubun no Hi",
648 NaiveDate::from_ymd_opt(year, 9, 23).expect("valid date components"),
649 0.02,
650 ));
651
652 let sports = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
654 cal.add_holiday(Holiday::new("Sports Day", sports, 0.05));
655
656 cal.add_holiday(Holiday::new(
658 "Bunka no Hi",
659 NaiveDate::from_ymd_opt(year, 11, 3).expect("valid date components"),
660 0.02,
661 ));
662
663 cal.add_holiday(Holiday::new(
665 "Kinro Kansha no Hi",
666 NaiveDate::from_ymd_opt(year, 11, 23).expect("valid date components"),
667 0.02,
668 ));
669
670 cal
671 }
672
673 fn in_holidays(year: i32) -> Self {
675 let mut cal = Self::new(Region::IN, year);
676
677 cal.add_holiday(Holiday::new(
679 "Republic Day",
680 NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components"),
681 0.02,
682 ));
683
684 cal.add_holiday(Holiday::new(
686 "Holi",
687 NaiveDate::from_ymd_opt(year, 3, 10).expect("valid date components"),
688 0.05,
689 ));
690
691 let easter = Self::easter_date(year);
693 cal.add_holiday(Holiday::new(
694 "Good Friday",
695 easter - Duration::days(2),
696 0.05,
697 ));
698
699 cal.add_holiday(Holiday::new(
701 "Independence Day",
702 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
703 0.02,
704 ));
705
706 cal.add_holiday(Holiday::new(
708 "Gandhi Jayanti",
709 NaiveDate::from_ymd_opt(year, 10, 2).expect("valid date components"),
710 0.02,
711 ));
712
713 cal.add_holiday(Holiday::new(
715 "Dussehra",
716 NaiveDate::from_ymd_opt(year, 10, 15).expect("valid date components"),
717 0.05,
718 ));
719
720 let diwali = Self::approximate_diwali(year);
722 for i in 0..5 {
723 cal.add_holiday(Holiday::new(
724 match i {
725 0 => "Dhanteras",
726 1 => "Naraka Chaturdashi",
727 2 => "Diwali",
728 3 => "Govardhan Puja",
729 _ => "Bhai Dooj",
730 },
731 diwali + Duration::days(i),
732 if i == 2 { 0.02 } else { 0.1 },
733 ));
734 }
735
736 cal.add_holiday(Holiday::new(
738 "Christmas",
739 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
740 0.1,
741 ));
742
743 cal
744 }
745
746 fn br_holidays(year: i32) -> Self {
748 let mut cal = Self::new(Region::BR, year);
749
750 cal.add_holiday(Holiday::new(
752 "Confraternização Universal",
753 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
754 0.02,
755 ));
756
757 let easter = Self::easter_date(year);
759 let carnival_tuesday = easter - Duration::days(47);
760 let carnival_monday = carnival_tuesday - Duration::days(1);
761 cal.add_holiday(Holiday::new("Carnaval (Segunda)", carnival_monday, 0.02));
762 cal.add_holiday(Holiday::new("Carnaval (Terça)", carnival_tuesday, 0.02));
763
764 cal.add_holiday(Holiday::new(
766 "Sexta-feira Santa",
767 easter - Duration::days(2),
768 0.02,
769 ));
770
771 cal.add_holiday(Holiday::new(
773 "Tiradentes",
774 NaiveDate::from_ymd_opt(year, 4, 21).expect("valid date components"),
775 0.02,
776 ));
777
778 cal.add_holiday(Holiday::new(
780 "Dia do Trabalho",
781 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
782 0.02,
783 ));
784
785 cal.add_holiday(Holiday::new(
787 "Corpus Christi",
788 easter + Duration::days(60),
789 0.05,
790 ));
791
792 cal.add_holiday(Holiday::new(
794 "Independência do Brasil",
795 NaiveDate::from_ymd_opt(year, 9, 7).expect("valid date components"),
796 0.02,
797 ));
798
799 cal.add_holiday(Holiday::new(
801 "Nossa Senhora Aparecida",
802 NaiveDate::from_ymd_opt(year, 10, 12).expect("valid date components"),
803 0.02,
804 ));
805
806 cal.add_holiday(Holiday::new(
808 "Finados",
809 NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
810 0.02,
811 ));
812
813 cal.add_holiday(Holiday::new(
815 "Proclamação da República",
816 NaiveDate::from_ymd_opt(year, 11, 15).expect("valid date components"),
817 0.02,
818 ));
819
820 cal.add_holiday(Holiday::new(
822 "Natal",
823 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
824 0.02,
825 ));
826
827 cal
828 }
829
830 fn mx_holidays(year: i32) -> Self {
832 let mut cal = Self::new(Region::MX, year);
833
834 cal.add_holiday(Holiday::new(
836 "Año Nuevo",
837 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
838 0.02,
839 ));
840
841 let constitution = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 1);
843 cal.add_holiday(Holiday::new("Día de la Constitución", constitution, 0.02));
844
845 let juarez = Self::nth_weekday_of_month(year, 3, Weekday::Mon, 3);
847 cal.add_holiday(Holiday::new("Natalicio de Benito Juárez", juarez, 0.02));
848
849 let easter = Self::easter_date(year);
851 cal.add_holiday(Holiday::new(
852 "Jueves Santo",
853 easter - Duration::days(3),
854 0.05,
855 ));
856 cal.add_holiday(Holiday::new(
857 "Viernes Santo",
858 easter - Duration::days(2),
859 0.02,
860 ));
861
862 cal.add_holiday(Holiday::new(
864 "Día del Trabajo",
865 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
866 0.02,
867 ));
868
869 cal.add_holiday(Holiday::new(
871 "Día de la Independencia",
872 NaiveDate::from_ymd_opt(year, 9, 16).expect("valid date components"),
873 0.02,
874 ));
875
876 let revolution = Self::nth_weekday_of_month(year, 11, Weekday::Mon, 3);
878 cal.add_holiday(Holiday::new("Día de la Revolución", revolution, 0.02));
879
880 cal.add_holiday(Holiday::new(
882 "Día de Muertos",
883 NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
884 0.1,
885 ));
886 cal.add_holiday(Holiday::new(
887 "Día de Muertos",
888 NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
889 0.1,
890 ));
891
892 cal.add_holiday(Holiday::new(
894 "Navidad",
895 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
896 0.02,
897 ));
898
899 cal
900 }
901
902 fn au_holidays(year: i32) -> Self {
904 let mut cal = Self::new(Region::AU, year);
905
906 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
908 cal.add_holiday(Holiday::new(
909 "New Year's Day",
910 Self::observe_weekend(new_years),
911 0.02,
912 ));
913
914 let australia_day = NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components");
916 cal.add_holiday(Holiday::new(
917 "Australia Day",
918 Self::observe_weekend(australia_day),
919 0.02,
920 ));
921
922 let easter = Self::easter_date(year);
924 cal.add_holiday(Holiday::new(
925 "Good Friday",
926 easter - Duration::days(2),
927 0.02,
928 ));
929
930 cal.add_holiday(Holiday::new(
932 "Easter Saturday",
933 easter - Duration::days(1),
934 0.02,
935 ));
936
937 cal.add_holiday(Holiday::new(
939 "Easter Monday",
940 easter + Duration::days(1),
941 0.02,
942 ));
943
944 let anzac = NaiveDate::from_ymd_opt(year, 4, 25).expect("valid date components");
946 cal.add_holiday(Holiday::new("ANZAC Day", anzac, 0.02));
947
948 let queens_birthday = Self::nth_weekday_of_month(year, 6, Weekday::Mon, 2);
950 cal.add_holiday(Holiday::new("Queen's Birthday", queens_birthday, 0.02));
951
952 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
954 cal.add_holiday(Holiday::new(
955 "Christmas Day",
956 Self::observe_weekend(christmas),
957 0.02,
958 ));
959
960 let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
962 cal.add_holiday(Holiday::new(
963 "Boxing Day",
964 Self::observe_weekend(boxing),
965 0.02,
966 ));
967
968 cal
969 }
970
971 fn sg_holidays(year: i32) -> Self {
973 let mut cal = Self::new(Region::SG, year);
974
975 cal.add_holiday(Holiday::new(
977 "New Year's Day",
978 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
979 0.02,
980 ));
981
982 let cny = Self::approximate_chinese_new_year(year);
984 cal.add_holiday(Holiday::new("Chinese New Year", cny, 0.02));
985 cal.add_holiday(Holiday::new(
986 "Chinese New Year (Day 2)",
987 cny + Duration::days(1),
988 0.02,
989 ));
990
991 let easter = Self::easter_date(year);
993 cal.add_holiday(Holiday::new(
994 "Good Friday",
995 easter - Duration::days(2),
996 0.02,
997 ));
998
999 cal.add_holiday(Holiday::new(
1001 "Labour Day",
1002 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1003 0.02,
1004 ));
1005
1006 let vesak = Self::approximate_vesak(year);
1008 cal.add_holiday(Holiday::new("Vesak Day", vesak, 0.02));
1009
1010 let hari_raya_puasa = Self::approximate_hari_raya_puasa(year);
1012 cal.add_holiday(Holiday::new("Hari Raya Puasa", hari_raya_puasa, 0.02));
1013
1014 let hari_raya_haji = Self::approximate_hari_raya_haji(year);
1016 cal.add_holiday(Holiday::new("Hari Raya Haji", hari_raya_haji, 0.02));
1017
1018 cal.add_holiday(Holiday::new(
1020 "National Day",
1021 NaiveDate::from_ymd_opt(year, 8, 9).expect("valid date components"),
1022 0.02,
1023 ));
1024
1025 let deepavali = Self::approximate_deepavali(year);
1027 cal.add_holiday(Holiday::new("Deepavali", deepavali, 0.02));
1028
1029 cal.add_holiday(Holiday::new(
1031 "Christmas Day",
1032 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1033 0.02,
1034 ));
1035
1036 cal
1037 }
1038
1039 fn kr_holidays(year: i32) -> Self {
1041 let mut cal = Self::new(Region::KR, year);
1042
1043 cal.add_holiday(Holiday::new(
1045 "Sinjeong",
1046 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
1047 0.02,
1048 ));
1049
1050 let seollal = Self::approximate_korean_new_year(year);
1052 cal.add_holiday(Holiday::new(
1053 "Seollal (Eve)",
1054 seollal - Duration::days(1),
1055 0.02,
1056 ));
1057 cal.add_holiday(Holiday::new("Seollal", seollal, 0.02));
1058 cal.add_holiday(Holiday::new(
1059 "Seollal (Day 2)",
1060 seollal + Duration::days(1),
1061 0.02,
1062 ));
1063
1064 cal.add_holiday(Holiday::new(
1066 "Samiljeol",
1067 NaiveDate::from_ymd_opt(year, 3, 1).expect("valid date components"),
1068 0.02,
1069 ));
1070
1071 cal.add_holiday(Holiday::new(
1073 "Eorininal",
1074 NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
1075 0.02,
1076 ));
1077
1078 let buddha_birthday = Self::approximate_korean_buddha_birthday(year);
1080 cal.add_holiday(Holiday::new("Seokgatansinil", buddha_birthday, 0.02));
1081
1082 cal.add_holiday(Holiday::new(
1084 "Hyeonchungil",
1085 NaiveDate::from_ymd_opt(year, 6, 6).expect("valid date components"),
1086 0.02,
1087 ));
1088
1089 cal.add_holiday(Holiday::new(
1091 "Gwangbokjeol",
1092 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
1093 0.02,
1094 ));
1095
1096 let chuseok = Self::approximate_chuseok(year);
1098 cal.add_holiday(Holiday::new(
1099 "Chuseok (Eve)",
1100 chuseok - Duration::days(1),
1101 0.02,
1102 ));
1103 cal.add_holiday(Holiday::new("Chuseok", chuseok, 0.02));
1104 cal.add_holiday(Holiday::new(
1105 "Chuseok (Day 2)",
1106 chuseok + Duration::days(1),
1107 0.02,
1108 ));
1109
1110 cal.add_holiday(Holiday::new(
1112 "Gaecheonjeol",
1113 NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1114 0.02,
1115 ));
1116
1117 cal.add_holiday(Holiday::new(
1119 "Hangullal",
1120 NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1121 0.02,
1122 ));
1123
1124 cal.add_holiday(Holiday::new(
1126 "Seongtanjeol",
1127 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1128 0.02,
1129 ));
1130
1131 cal
1132 }
1133
1134 fn fr_holidays(year: i32) -> Self {
1136 let mut cal = Self::new(Region::FR, year);
1137
1138 cal.add_holiday(Holiday::new(
1140 "Jour de l'an",
1141 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
1142 0.02,
1143 ));
1144
1145 let easter = Self::easter_date(year);
1146
1147 cal.add_holiday(Holiday::new(
1149 "Lundi de Pâques",
1150 easter + Duration::days(1),
1151 0.02,
1152 ));
1153
1154 cal.add_holiday(Holiday::new(
1156 "Fête du Travail",
1157 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1158 0.02,
1159 ));
1160
1161 cal.add_holiday(Holiday::new(
1163 "Victoire 1945",
1164 NaiveDate::from_ymd_opt(year, 5, 8).expect("valid date components"),
1165 0.02,
1166 ));
1167
1168 cal.add_holiday(Holiday::new("Ascension", easter + Duration::days(39), 0.02));
1170
1171 cal.add_holiday(Holiday::new(
1173 "Lundi de Pentecôte",
1174 easter + Duration::days(50),
1175 0.05,
1176 ));
1177
1178 cal.add_holiday(Holiday::new(
1180 "Fête nationale",
1181 NaiveDate::from_ymd_opt(year, 7, 14).expect("valid date components"),
1182 0.02,
1183 ));
1184
1185 cal.add_holiday(Holiday::new(
1187 "Assomption",
1188 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
1189 0.02,
1190 ));
1191
1192 cal.add_holiday(Holiday::new(
1194 "Toussaint",
1195 NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1196 0.02,
1197 ));
1198
1199 cal.add_holiday(Holiday::new(
1201 "Armistice",
1202 NaiveDate::from_ymd_opt(year, 11, 11).expect("valid date components"),
1203 0.02,
1204 ));
1205
1206 cal.add_holiday(Holiday::new(
1208 "Noël",
1209 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1210 0.02,
1211 ));
1212
1213 cal
1214 }
1215
1216 fn it_holidays(year: i32) -> Self {
1218 let mut cal = Self::new(Region::IT, year);
1219
1220 cal.add_holiday(Holiday::new(
1222 "Capodanno",
1223 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
1224 0.02,
1225 ));
1226
1227 cal.add_holiday(Holiday::new(
1229 "Epifania",
1230 NaiveDate::from_ymd_opt(year, 1, 6).expect("valid date components"),
1231 0.02,
1232 ));
1233
1234 let easter = Self::easter_date(year);
1235
1236 cal.add_holiday(Holiday::new(
1238 "Lunedì dell'Angelo",
1239 easter + Duration::days(1),
1240 0.02,
1241 ));
1242
1243 cal.add_holiday(Holiday::new(
1245 "Festa della Liberazione",
1246 NaiveDate::from_ymd_opt(year, 4, 25).expect("valid date components"),
1247 0.02,
1248 ));
1249
1250 cal.add_holiday(Holiday::new(
1252 "Festa dei Lavoratori",
1253 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1254 0.02,
1255 ));
1256
1257 cal.add_holiday(Holiday::new(
1259 "Festa della Repubblica",
1260 NaiveDate::from_ymd_opt(year, 6, 2).expect("valid date components"),
1261 0.02,
1262 ));
1263
1264 cal.add_holiday(Holiday::new(
1266 "Ferragosto",
1267 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
1268 0.02,
1269 ));
1270
1271 cal.add_holiday(Holiday::new(
1273 "Tutti i Santi",
1274 NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1275 0.02,
1276 ));
1277
1278 cal.add_holiday(Holiday::new(
1280 "Immacolata Concezione",
1281 NaiveDate::from_ymd_opt(year, 12, 8).expect("valid date components"),
1282 0.02,
1283 ));
1284
1285 cal.add_holiday(Holiday::new(
1287 "Natale",
1288 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1289 0.02,
1290 ));
1291
1292 cal.add_holiday(Holiday::new(
1294 "Santo Stefano",
1295 NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components"),
1296 0.02,
1297 ));
1298
1299 cal
1300 }
1301
1302 fn es_holidays(year: i32) -> Self {
1304 let mut cal = Self::new(Region::ES, year);
1305
1306 cal.add_holiday(Holiday::new(
1308 "Año Nuevo",
1309 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
1310 0.02,
1311 ));
1312
1313 cal.add_holiday(Holiday::new(
1315 "Epifanía del Señor",
1316 NaiveDate::from_ymd_opt(year, 1, 6).expect("valid date components"),
1317 0.02,
1318 ));
1319
1320 let easter = Self::easter_date(year);
1321
1322 cal.add_holiday(Holiday::new(
1324 "Viernes Santo",
1325 easter - Duration::days(2),
1326 0.02,
1327 ));
1328
1329 cal.add_holiday(Holiday::new(
1331 "Fiesta del Trabajo",
1332 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1333 0.02,
1334 ));
1335
1336 cal.add_holiday(Holiday::new(
1338 "Asunción de la Virgen",
1339 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
1340 0.02,
1341 ));
1342
1343 cal.add_holiday(Holiday::new(
1345 "Fiesta Nacional de España",
1346 NaiveDate::from_ymd_opt(year, 10, 12).expect("valid date components"),
1347 0.02,
1348 ));
1349
1350 cal.add_holiday(Holiday::new(
1352 "Todos los Santos",
1353 NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1354 0.02,
1355 ));
1356
1357 cal.add_holiday(Holiday::new(
1359 "Día de la Constitución",
1360 NaiveDate::from_ymd_opt(year, 12, 6).expect("valid date components"),
1361 0.02,
1362 ));
1363
1364 cal.add_holiday(Holiday::new(
1366 "Inmaculada Concepción",
1367 NaiveDate::from_ymd_opt(year, 12, 8).expect("valid date components"),
1368 0.02,
1369 ));
1370
1371 cal.add_holiday(Holiday::new(
1373 "Navidad",
1374 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1375 0.02,
1376 ));
1377
1378 cal
1379 }
1380
1381 fn ca_holidays(year: i32) -> Self {
1383 let mut cal = Self::new(Region::CA, year);
1384
1385 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
1387 cal.add_holiday(Holiday::new(
1388 "New Year's Day",
1389 Self::observe_weekend(new_years),
1390 0.02,
1391 ));
1392
1393 let easter = Self::easter_date(year);
1394
1395 cal.add_holiday(Holiday::new(
1397 "Good Friday",
1398 easter - Duration::days(2),
1399 0.02,
1400 ));
1401
1402 let may24 = NaiveDate::from_ymd_opt(year, 5, 24).expect("valid date components");
1404 let victoria_day = {
1405 let wd = may24.weekday();
1406 let days_back = (wd.num_days_from_monday() as i64 + 7) % 7;
1407 may24 - Duration::days(days_back)
1408 };
1409 cal.add_holiday(Holiday::new("Victoria Day", victoria_day, 0.02));
1410
1411 let canada_day = NaiveDate::from_ymd_opt(year, 7, 1).expect("valid date components");
1413 cal.add_holiday(Holiday::new(
1414 "Canada Day",
1415 Self::observe_weekend(canada_day),
1416 0.02,
1417 ));
1418
1419 let labour_day = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 1);
1421 cal.add_holiday(Holiday::new("Labour Day", labour_day, 0.02));
1422
1423 let truth_recon = NaiveDate::from_ymd_opt(year, 9, 30).expect("valid date components");
1425 cal.add_holiday(Holiday::new(
1426 "National Day for Truth and Reconciliation",
1427 Self::observe_weekend(truth_recon),
1428 0.02,
1429 ));
1430
1431 let thanksgiving = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
1433 cal.add_holiday(Holiday::new("Thanksgiving", thanksgiving, 0.02));
1434
1435 let remembrance = NaiveDate::from_ymd_opt(year, 11, 11).expect("valid date components");
1437 cal.add_holiday(Holiday::new(
1438 "Remembrance Day",
1439 Self::observe_weekend(remembrance),
1440 0.02,
1441 ));
1442
1443 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
1445 cal.add_holiday(Holiday::new(
1446 "Christmas Day",
1447 Self::observe_weekend(christmas),
1448 0.02,
1449 ));
1450
1451 let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
1453 cal.add_holiday(Holiday::new(
1454 "Boxing Day",
1455 Self::observe_weekend(boxing),
1456 0.02,
1457 ));
1458
1459 cal
1460 }
1461
1462 fn easter_date(year: i32) -> NaiveDate {
1464 let a = year % 19;
1465 let b = year / 100;
1466 let c = year % 100;
1467 let d = b / 4;
1468 let e = b % 4;
1469 let f = (b + 8) / 25;
1470 let g = (b - f + 1) / 3;
1471 let h = (19 * a + b - d - g + 15) % 30;
1472 let i = c / 4;
1473 let k = c % 4;
1474 let l = (32 + 2 * e + 2 * i - h - k) % 7;
1475 let m = (a + 11 * h + 22 * l) / 451;
1476 let month = (h + l - 7 * m + 114) / 31;
1477 let day = ((h + l - 7 * m + 114) % 31) + 1;
1478
1479 NaiveDate::from_ymd_opt(year, month as u32, day as u32).expect("valid date components")
1480 }
1481
1482 fn nth_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u32) -> NaiveDate {
1484 let first = NaiveDate::from_ymd_opt(year, month, 1).expect("valid date components");
1485 let first_weekday = first.weekday();
1486
1487 let days_until = (weekday.num_days_from_monday() as i64
1488 - first_weekday.num_days_from_monday() as i64
1489 + 7)
1490 % 7;
1491
1492 first + Duration::days(days_until + (n - 1) as i64 * 7)
1493 }
1494
1495 fn last_weekday_of_month(year: i32, month: u32, weekday: Weekday) -> NaiveDate {
1497 let last = if month == 12 {
1498 NaiveDate::from_ymd_opt(year + 1, 1, 1).expect("valid date components")
1499 - Duration::days(1)
1500 } else {
1501 NaiveDate::from_ymd_opt(year, month + 1, 1).expect("valid date components")
1502 - Duration::days(1)
1503 };
1504
1505 let last_weekday = last.weekday();
1506 let days_back = (last_weekday.num_days_from_monday() as i64
1507 - weekday.num_days_from_monday() as i64
1508 + 7)
1509 % 7;
1510
1511 last - Duration::days(days_back)
1512 }
1513
1514 fn observe_weekend(date: NaiveDate) -> NaiveDate {
1516 match date.weekday() {
1517 Weekday::Sat => date - Duration::days(1), Weekday::Sun => date + Duration::days(1), _ => date,
1520 }
1521 }
1522
1523 fn approximate_chinese_new_year(year: i32) -> NaiveDate {
1525 let base_year = 2000;
1528 let cny_2000 = NaiveDate::from_ymd_opt(2000, 2, 5).expect("valid date components");
1529
1530 let years_diff = year - base_year;
1531 let lunar_cycle = 29.5306; let days_offset = (years_diff as f64 * 12.0 * lunar_cycle) % 365.25;
1533
1534 let mut result = cny_2000 + Duration::days(days_offset as i64);
1535
1536 while result.month() > 2 || (result.month() == 2 && result.day() > 20) {
1538 result -= Duration::days(29);
1539 }
1540 while result.month() < 1 || (result.month() == 1 && result.day() < 21) {
1541 result += Duration::days(29);
1542 }
1543
1544 if result.year() != year {
1546 result = NaiveDate::from_ymd_opt(year, result.month(), result.day().min(28))
1547 .unwrap_or_else(|| {
1548 NaiveDate::from_ymd_opt(year, result.month(), 28)
1549 .expect("valid date components")
1550 });
1551 }
1552
1553 result
1554 }
1555
1556 fn approximate_diwali(year: i32) -> NaiveDate {
1558 match year % 4 {
1561 0 => NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1562 1 => NaiveDate::from_ymd_opt(year, 10, 24).expect("valid date components"),
1563 2 => NaiveDate::from_ymd_opt(year, 11, 12).expect("valid date components"),
1564 _ => NaiveDate::from_ymd_opt(year, 11, 4).expect("valid date components"),
1565 }
1566 }
1567
1568 fn approximate_vesak(year: i32) -> NaiveDate {
1571 let base = match year % 19 {
1574 0 => 18,
1575 1 => 7,
1576 2 => 26,
1577 3 => 15,
1578 4 => 5,
1579 5 => 24,
1580 6 => 13,
1581 7 => 2,
1582 8 => 22,
1583 9 => 11,
1584 10 => 30,
1585 11 => 19,
1586 12 => 8,
1587 13 => 27,
1588 14 => 17,
1589 15 => 6,
1590 16 => 25,
1591 17 => 14,
1592 _ => 3,
1593 };
1594 let month = if base > 20 { 4 } else { 5 };
1595 let day = if base > 20 { base - 10 } else { base };
1596 NaiveDate::from_ymd_opt(year, month, day.clamp(1, 28) as u32)
1597 .expect("valid date components")
1598 }
1599
1600 fn approximate_hari_raya_puasa(year: i32) -> NaiveDate {
1603 let base_year = 2024;
1606 let base_date = NaiveDate::from_ymd_opt(2024, 4, 10).expect("valid date components");
1607 let years_diff = year - base_year;
1608 let days_shift = (years_diff as f64 * -10.63) as i64;
1609 let mut result = base_date + Duration::days(days_shift);
1610
1611 while result.year() != year {
1613 if result.year() > year {
1614 result -= Duration::days(354); } else {
1616 result += Duration::days(354);
1617 }
1618 }
1619 result
1620 }
1621
1622 fn approximate_hari_raya_haji(year: i32) -> NaiveDate {
1625 Self::approximate_hari_raya_puasa(year) + Duration::days(70)
1626 }
1627
1628 fn approximate_deepavali(year: i32) -> NaiveDate {
1630 Self::approximate_diwali(year)
1631 }
1632
1633 fn approximate_korean_new_year(year: i32) -> NaiveDate {
1636 Self::approximate_chinese_new_year(year)
1637 }
1638
1639 fn approximate_korean_buddha_birthday(year: i32) -> NaiveDate {
1642 match year % 19 {
1644 0 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1645 1 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1646 2 => NaiveDate::from_ymd_opt(year, 5, 23).expect("valid date components"),
1647 3 => NaiveDate::from_ymd_opt(year, 5, 12).expect("valid date components"),
1648 4 => NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1649 5 => NaiveDate::from_ymd_opt(year, 5, 20).expect("valid date components"),
1650 6 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1651 7 => NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
1652 8 => NaiveDate::from_ymd_opt(year, 5, 18).expect("valid date components"),
1653 9 => NaiveDate::from_ymd_opt(year, 5, 7).expect("valid date components"),
1654 10 => NaiveDate::from_ymd_opt(year, 5, 26).expect("valid date components"),
1655 11 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1656 12 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1657 13 => NaiveDate::from_ymd_opt(year, 5, 24).expect("valid date components"),
1658 14 => NaiveDate::from_ymd_opt(year, 5, 13).expect("valid date components"),
1659 15 => NaiveDate::from_ymd_opt(year, 5, 2).expect("valid date components"),
1660 16 => NaiveDate::from_ymd_opt(year, 5, 21).expect("valid date components"),
1661 17 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1662 _ => NaiveDate::from_ymd_opt(year, 4, 30).expect("valid date components"),
1663 }
1664 }
1665
1666 fn approximate_chuseok(year: i32) -> NaiveDate {
1669 match year % 19 {
1671 0 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1672 1 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1673 2 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1674 3 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1675 4 => NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1676 5 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1677 6 => NaiveDate::from_ymd_opt(year, 9, 11).expect("valid date components"),
1678 7 => NaiveDate::from_ymd_opt(year, 9, 30).expect("valid date components"),
1679 8 => NaiveDate::from_ymd_opt(year, 9, 19).expect("valid date components"),
1680 9 => NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1681 10 => NaiveDate::from_ymd_opt(year, 9, 28).expect("valid date components"),
1682 11 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1683 12 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1684 13 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1685 14 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1686 15 => NaiveDate::from_ymd_opt(year, 10, 4).expect("valid date components"),
1687 16 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1688 17 => NaiveDate::from_ymd_opt(year, 9, 12).expect("valid date components"),
1689 _ => NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components"),
1690 }
1691 }
1692}
1693
1694#[derive(Debug, Clone, Serialize, Deserialize)]
1696pub struct CustomHolidayConfig {
1697 pub name: String,
1699 pub month: u8,
1701 pub day: u8,
1703 #[serde(default = "default_holiday_multiplier")]
1705 pub activity_multiplier: f64,
1706}
1707
1708fn default_holiday_multiplier() -> f64 {
1709 0.05
1710}
1711
1712impl CustomHolidayConfig {
1713 pub fn to_holiday(&self, year: i32) -> Holiday {
1715 Holiday::new(
1716 &self.name,
1717 NaiveDate::from_ymd_opt(year, self.month as u32, self.day as u32)
1718 .expect("valid date components"),
1719 self.activity_multiplier,
1720 )
1721 }
1722}
1723
1724#[cfg(test)]
1725#[allow(clippy::unwrap_used)]
1726mod tests {
1727 use super::*;
1728
1729 #[test]
1730 fn test_us_holidays() {
1731 let cal = HolidayCalendar::for_region(Region::US, 2024);
1732
1733 let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1735 assert!(cal.is_holiday(christmas));
1736
1737 let independence = NaiveDate::from_ymd_opt(2024, 7, 4).unwrap();
1739 assert!(cal.is_holiday(independence));
1740 }
1741
1742 #[test]
1743 fn test_german_holidays() {
1744 let cal = HolidayCalendar::for_region(Region::DE, 2024);
1745
1746 let unity = NaiveDate::from_ymd_opt(2024, 10, 3).unwrap();
1748 assert!(cal.is_holiday(unity));
1749 }
1750
1751 #[test]
1752 fn test_easter_calculation() {
1753 assert_eq!(
1755 HolidayCalendar::easter_date(2024),
1756 NaiveDate::from_ymd_opt(2024, 3, 31).unwrap()
1757 );
1758 assert_eq!(
1759 HolidayCalendar::easter_date(2025),
1760 NaiveDate::from_ymd_opt(2025, 4, 20).unwrap()
1761 );
1762 }
1763
1764 #[test]
1765 fn test_nth_weekday() {
1766 let mlk = HolidayCalendar::nth_weekday_of_month(2024, 1, Weekday::Mon, 3);
1768 assert_eq!(mlk, NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1769
1770 let thanksgiving = HolidayCalendar::nth_weekday_of_month(2024, 11, Weekday::Thu, 4);
1772 assert_eq!(thanksgiving, NaiveDate::from_ymd_opt(2024, 11, 28).unwrap());
1773 }
1774
1775 #[test]
1776 fn test_last_weekday() {
1777 let memorial = HolidayCalendar::last_weekday_of_month(2024, 5, Weekday::Mon);
1779 assert_eq!(memorial, NaiveDate::from_ymd_opt(2024, 5, 27).unwrap());
1780 }
1781
1782 #[test]
1783 fn test_activity_multiplier() {
1784 let cal = HolidayCalendar::for_region(Region::US, 2024);
1785
1786 let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1788 assert!(cal.get_multiplier(christmas) < 0.1);
1789
1790 let regular = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
1792 assert!((cal.get_multiplier(regular) - 1.0).abs() < 0.01);
1793 }
1794
1795 #[test]
1796 fn test_all_regions_have_holidays() {
1797 let regions = [
1798 Region::US,
1799 Region::DE,
1800 Region::GB,
1801 Region::CN,
1802 Region::JP,
1803 Region::IN,
1804 Region::BR,
1805 Region::MX,
1806 Region::AU,
1807 Region::SG,
1808 Region::KR,
1809 Region::FR,
1810 Region::IT,
1811 Region::ES,
1812 Region::CA,
1813 ];
1814
1815 for region in regions {
1816 let cal = HolidayCalendar::for_region(region, 2024);
1817 assert!(
1818 !cal.holidays.is_empty(),
1819 "Region {:?} should have holidays",
1820 region
1821 );
1822 }
1823 }
1824
1825 #[test]
1826 fn test_brazilian_holidays() {
1827 let cal = HolidayCalendar::for_region(Region::BR, 2024);
1828
1829 let independence = NaiveDate::from_ymd_opt(2024, 9, 7).unwrap();
1831 assert!(cal.is_holiday(independence));
1832
1833 let tiradentes = NaiveDate::from_ymd_opt(2024, 4, 21).unwrap();
1835 assert!(cal.is_holiday(tiradentes));
1836 }
1837
1838 #[test]
1839 fn test_mexican_holidays() {
1840 let cal = HolidayCalendar::for_region(Region::MX, 2024);
1841
1842 let independence = NaiveDate::from_ymd_opt(2024, 9, 16).unwrap();
1844 assert!(cal.is_holiday(independence));
1845 }
1846
1847 #[test]
1848 fn test_australian_holidays() {
1849 let cal = HolidayCalendar::for_region(Region::AU, 2024);
1850
1851 let anzac = NaiveDate::from_ymd_opt(2024, 4, 25).unwrap();
1853 assert!(cal.is_holiday(anzac));
1854
1855 let australia_day = NaiveDate::from_ymd_opt(2024, 1, 26).unwrap();
1857 assert!(cal.is_holiday(australia_day));
1858 }
1859
1860 #[test]
1861 fn test_singapore_holidays() {
1862 let cal = HolidayCalendar::for_region(Region::SG, 2024);
1863
1864 let national = NaiveDate::from_ymd_opt(2024, 8, 9).unwrap();
1866 assert!(cal.is_holiday(national));
1867 }
1868
1869 #[test]
1870 fn test_korean_holidays() {
1871 let cal = HolidayCalendar::for_region(Region::KR, 2024);
1872
1873 let liberation = NaiveDate::from_ymd_opt(2024, 8, 15).unwrap();
1875 assert!(cal.is_holiday(liberation));
1876
1877 let hangul = NaiveDate::from_ymd_opt(2024, 10, 9).unwrap();
1879 assert!(cal.is_holiday(hangul));
1880 }
1881
1882 #[test]
1883 fn test_chinese_holidays() {
1884 let cal = HolidayCalendar::for_region(Region::CN, 2024);
1885
1886 let national = NaiveDate::from_ymd_opt(2024, 10, 1).unwrap();
1888 assert!(cal.is_holiday(national));
1889 }
1890
1891 #[test]
1892 fn test_japanese_golden_week() {
1893 let cal = HolidayCalendar::for_region(Region::JP, 2024);
1894
1895 let kodomo = NaiveDate::from_ymd_opt(2024, 5, 5).unwrap();
1897 assert!(cal.is_holiday(kodomo));
1898 }
1899
1900 #[test]
1901 fn test_french_holidays() {
1902 let cal = HolidayCalendar::for_region(Region::FR, 2024);
1903
1904 let bastille = NaiveDate::from_ymd_opt(2024, 7, 14).unwrap();
1906 assert!(cal.is_holiday(bastille));
1907
1908 let noel = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1910 assert!(cal.is_holiday(noel));
1911
1912 let travail = NaiveDate::from_ymd_opt(2024, 5, 1).unwrap();
1914 assert!(cal.is_holiday(travail));
1915
1916 let easter_monday = NaiveDate::from_ymd_opt(2024, 4, 1).unwrap();
1918 assert!(cal.is_holiday(easter_monday));
1919
1920 assert_eq!(cal.holidays.len(), 11);
1922 }
1923
1924 #[test]
1925 fn test_french_holidays_2025() {
1926 let cal = HolidayCalendar::for_region(Region::FR, 2025);
1927
1928 let easter_monday = NaiveDate::from_ymd_opt(2025, 4, 21).unwrap();
1930 assert!(cal.is_holiday(easter_monday));
1931
1932 let ascension = NaiveDate::from_ymd_opt(2025, 5, 29).unwrap();
1934 assert!(cal.is_holiday(ascension));
1935 }
1936
1937 #[test]
1938 fn test_italian_holidays() {
1939 let cal = HolidayCalendar::for_region(Region::IT, 2024);
1940
1941 let ferragosto = NaiveDate::from_ymd_opt(2024, 8, 15).unwrap();
1943 assert!(cal.is_holiday(ferragosto));
1944
1945 let repubblica = NaiveDate::from_ymd_opt(2024, 6, 2).unwrap();
1947 assert!(cal.is_holiday(repubblica));
1948
1949 let stefano = NaiveDate::from_ymd_opt(2024, 12, 26).unwrap();
1951 assert!(cal.is_holiday(stefano));
1952
1953 let epifania = NaiveDate::from_ymd_opt(2024, 1, 6).unwrap();
1955 assert!(cal.is_holiday(epifania));
1956
1957 assert_eq!(cal.holidays.len(), 11);
1959 }
1960
1961 #[test]
1962 fn test_spanish_holidays() {
1963 let cal = HolidayCalendar::for_region(Region::ES, 2024);
1964
1965 let national = NaiveDate::from_ymd_opt(2024, 10, 12).unwrap();
1967 assert!(cal.is_holiday(national));
1968
1969 let constitution = NaiveDate::from_ymd_opt(2024, 12, 6).unwrap();
1971 assert!(cal.is_holiday(constitution));
1972
1973 let good_friday = NaiveDate::from_ymd_opt(2024, 3, 29).unwrap();
1975 assert!(cal.is_holiday(good_friday));
1976
1977 assert_eq!(cal.holidays.len(), 10);
1979 }
1980
1981 #[test]
1982 fn test_canadian_holidays() {
1983 let cal = HolidayCalendar::for_region(Region::CA, 2024);
1984
1985 let canada_day = NaiveDate::from_ymd_opt(2024, 7, 1).unwrap();
1987 assert!(cal.is_holiday(canada_day));
1988
1989 let thanksgiving = NaiveDate::from_ymd_opt(2024, 10, 14).unwrap();
1991 assert!(cal.is_holiday(thanksgiving));
1992
1993 let victoria = NaiveDate::from_ymd_opt(2024, 5, 20).unwrap();
1995 assert!(cal.is_holiday(victoria));
1996
1997 let labour = NaiveDate::from_ymd_opt(2024, 9, 2).unwrap();
1999 assert!(cal.is_holiday(labour));
2000
2001 assert_eq!(cal.holidays.len(), 10);
2003 }
2004
2005 #[test]
2006 fn test_canadian_holidays_2025() {
2007 let cal = HolidayCalendar::for_region(Region::CA, 2025);
2008
2009 let victoria = NaiveDate::from_ymd_opt(2025, 5, 19).unwrap();
2011 assert!(cal.is_holiday(victoria));
2012
2013 let thanksgiving = NaiveDate::from_ymd_opt(2025, 10, 13).unwrap();
2015 assert!(cal.is_holiday(thanksgiving));
2016 }
2017
2018 fn sorted_dates(cal: &HolidayCalendar) -> Vec<NaiveDate> {
2024 let mut dates = cal.all_dates();
2025 dates.sort();
2026 dates.dedup();
2027 dates
2028 }
2029
2030 #[test]
2031 fn test_us_country_pack_parity_2024() {
2032 let reg = crate::CountryPackRegistry::builtin_only().expect("builtin registry");
2033 let us_pack = reg.get_by_str("US");
2034
2035 let legacy = HolidayCalendar::for_region(Region::US, 2024);
2036 let pack_cal = HolidayCalendar::from_country_pack(us_pack, 2024);
2037
2038 let legacy_dates = sorted_dates(&legacy);
2039 let pack_dates = sorted_dates(&pack_cal);
2040
2041 for date in &legacy_dates {
2043 assert!(
2044 pack_cal.is_holiday(*date),
2045 "US pack calendar missing legacy holiday on {date}"
2046 );
2047 }
2048
2049 for date in &pack_dates {
2051 assert!(
2052 legacy.is_holiday(*date),
2053 "Legacy US calendar missing pack holiday on {date}"
2054 );
2055 }
2056
2057 assert_eq!(
2058 legacy_dates.len(),
2059 pack_dates.len(),
2060 "US holiday count mismatch: legacy={}, pack={}",
2061 legacy_dates.len(),
2062 pack_dates.len()
2063 );
2064 }
2065
2066 #[test]
2067 fn test_us_country_pack_parity_2025() {
2068 let reg = crate::CountryPackRegistry::builtin_only().expect("builtin registry");
2069 let us_pack = reg.get_by_str("US");
2070
2071 let legacy = HolidayCalendar::for_region(Region::US, 2025);
2072 let pack_cal = HolidayCalendar::from_country_pack(us_pack, 2025);
2073
2074 let legacy_dates = sorted_dates(&legacy);
2075 let pack_dates = sorted_dates(&pack_cal);
2076
2077 for date in &legacy_dates {
2078 assert!(
2079 pack_cal.is_holiday(*date),
2080 "US 2025 pack calendar missing legacy holiday on {date}"
2081 );
2082 }
2083 for date in &pack_dates {
2084 assert!(
2085 legacy.is_holiday(*date),
2086 "Legacy US 2025 calendar missing pack holiday on {date}"
2087 );
2088 }
2089 assert_eq!(legacy_dates.len(), pack_dates.len());
2090 }
2091
2092 #[test]
2093 fn test_de_country_pack_parity_2024() {
2094 let reg = crate::CountryPackRegistry::builtin_only().expect("builtin registry");
2095 let de_pack = reg.get_by_str("DE");
2096
2097 let legacy = HolidayCalendar::for_region(Region::DE, 2024);
2098 let pack_cal = HolidayCalendar::from_country_pack(de_pack, 2024);
2099
2100 let legacy_dates = sorted_dates(&legacy);
2101 let pack_dates = sorted_dates(&pack_cal);
2102
2103 for date in &legacy_dates {
2104 assert!(
2105 pack_cal.is_holiday(*date),
2106 "DE pack calendar missing legacy holiday on {date}"
2107 );
2108 }
2109 for date in &pack_dates {
2110 assert!(
2111 legacy.is_holiday(*date),
2112 "Legacy DE calendar missing pack holiday on {date}"
2113 );
2114 }
2115 assert_eq!(
2116 legacy_dates.len(),
2117 pack_dates.len(),
2118 "DE holiday count mismatch: legacy={}, pack={}",
2119 legacy_dates.len(),
2120 pack_dates.len()
2121 );
2122 }
2123
2124 #[test]
2125 fn test_gb_country_pack_parity_2024() {
2126 let reg = crate::CountryPackRegistry::builtin_only().expect("builtin registry");
2127 let gb_pack = reg.get_by_str("GB");
2128
2129 let legacy = HolidayCalendar::for_region(Region::GB, 2024);
2130 let pack_cal = HolidayCalendar::from_country_pack(gb_pack, 2024);
2131
2132 let legacy_dates = sorted_dates(&legacy);
2133 let pack_dates = sorted_dates(&pack_cal);
2134
2135 for date in &legacy_dates {
2136 assert!(
2137 pack_cal.is_holiday(*date),
2138 "GB pack calendar missing legacy holiday on {date}"
2139 );
2140 }
2141 for date in &pack_dates {
2142 assert!(
2143 legacy.is_holiday(*date),
2144 "Legacy GB calendar missing pack holiday on {date}"
2145 );
2146 }
2147 assert_eq!(
2148 legacy_dates.len(),
2149 pack_dates.len(),
2150 "GB holiday count mismatch: legacy={}, pack={}",
2151 legacy_dates.len(),
2152 pack_dates.len()
2153 );
2154 }
2155}