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}
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#[derive(Debug, Clone)]
57pub struct Holiday {
58 pub name: String,
60 pub date: NaiveDate,
62 pub activity_multiplier: f64,
64 pub is_bank_holiday: bool,
66}
67
68impl Holiday {
69 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 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#[derive(Debug, Clone)]
88pub struct HolidayCalendar {
89 pub region: Region,
91 pub year: i32,
93 pub holidays: Vec<Holiday>,
95}
96
97impl HolidayCalendar {
98 pub fn new(region: Region, year: i32) -> Self {
100 Self {
101 region,
102 year,
103 holidays: Vec::new(),
104 }
105 }
106
107 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 pub fn is_holiday(&self, date: NaiveDate) -> bool {
126 self.holidays.iter().any(|h| h.date == date)
127 }
128
129 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 pub fn get_holidays(&self, date: NaiveDate) -> Vec<&Holiday> {
140 self.holidays.iter().filter(|h| h.date == date).collect()
141 }
142
143 pub fn add_holiday(&mut self, holiday: Holiday) {
145 self.holidays.push(holiday);
146 }
147
148 pub fn all_dates(&self) -> Vec<NaiveDate> {
150 self.holidays.iter().map(|h| h.date).collect()
151 }
152
153 fn us_holidays(year: i32) -> Self {
155 let mut cal = Self::new(Region::US, year);
156
157 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
159 cal.add_holiday(Holiday::new(
160 "New Year's Day",
161 Self::observe_weekend(new_years),
162 0.02,
163 ));
164
165 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 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 let memorial = Self::last_weekday_of_month(year, 5, Weekday::Mon);
175 cal.add_holiday(Holiday::new("Memorial Day", memorial, 0.05));
176
177 let juneteenth = NaiveDate::from_ymd_opt(year, 6, 19).expect("valid date components");
179 cal.add_holiday(Holiday::new(
180 "Juneteenth",
181 Self::observe_weekend(juneteenth),
182 0.1,
183 ));
184
185 let independence = NaiveDate::from_ymd_opt(year, 7, 4).expect("valid date components");
187 cal.add_holiday(Holiday::new(
188 "Independence Day",
189 Self::observe_weekend(independence),
190 0.02,
191 ));
192
193 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 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 let veterans = NaiveDate::from_ymd_opt(year, 11, 11).expect("valid date components");
203 cal.add_holiday(Holiday::new(
204 "Veterans Day",
205 Self::observe_weekend(veterans),
206 0.1,
207 ));
208
209 let thanksgiving = Self::nth_weekday_of_month(year, 11, Weekday::Thu, 4);
211 cal.add_holiday(Holiday::new("Thanksgiving", thanksgiving, 0.02));
212
213 cal.add_holiday(Holiday::new(
215 "Day after Thanksgiving",
216 thanksgiving + Duration::days(1),
217 0.1,
218 ));
219
220 let christmas_eve = NaiveDate::from_ymd_opt(year, 12, 24).expect("valid date components");
222 cal.add_holiday(Holiday::new("Christmas Eve", christmas_eve, 0.1));
223
224 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
226 cal.add_holiday(Holiday::new(
227 "Christmas Day",
228 Self::observe_weekend(christmas),
229 0.02,
230 ));
231
232 let new_years_eve = NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components");
234 cal.add_holiday(Holiday::new("New Year's Eve", new_years_eve, 0.1));
235
236 cal
237 }
238
239 fn de_holidays(year: i32) -> Self {
241 let mut cal = Self::new(Region::DE, year);
242
243 cal.add_holiday(Holiday::new(
245 "Neujahr",
246 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
247 0.02,
248 ));
249
250 let easter = Self::easter_date(year);
252 cal.add_holiday(Holiday::new("Karfreitag", easter - Duration::days(2), 0.02));
253
254 cal.add_holiday(Holiday::new(
256 "Ostermontag",
257 easter + Duration::days(1),
258 0.02,
259 ));
260
261 cal.add_holiday(Holiday::new(
263 "Tag der Arbeit",
264 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
265 0.02,
266 ));
267
268 cal.add_holiday(Holiday::new(
270 "Christi Himmelfahrt",
271 easter + Duration::days(39),
272 0.02,
273 ));
274
275 cal.add_holiday(Holiday::new(
277 "Pfingstmontag",
278 easter + Duration::days(50),
279 0.02,
280 ));
281
282 cal.add_holiday(Holiday::new(
284 "Tag der Deutschen Einheit",
285 NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
286 0.02,
287 ));
288
289 cal.add_holiday(Holiday::new(
291 "1. Weihnachtstag",
292 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
293 0.02,
294 ));
295 cal.add_holiday(Holiday::new(
296 "2. Weihnachtstag",
297 NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components"),
298 0.02,
299 ));
300
301 cal.add_holiday(Holiday::new(
303 "Silvester",
304 NaiveDate::from_ymd_opt(year, 12, 31).expect("valid date components"),
305 0.1,
306 ));
307
308 cal
309 }
310
311 fn gb_holidays(year: i32) -> Self {
313 let mut cal = Self::new(Region::GB, year);
314
315 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
317 cal.add_holiday(Holiday::new(
318 "New Year's Day",
319 Self::observe_weekend(new_years),
320 0.02,
321 ));
322
323 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 cal.add_holiday(Holiday::new(
333 "Easter Monday",
334 easter + Duration::days(1),
335 0.02,
336 ));
337
338 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 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 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 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
352 cal.add_holiday(Holiday::new(
353 "Christmas Day",
354 Self::observe_weekend(christmas),
355 0.02,
356 ));
357
358 let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
360 cal.add_holiday(Holiday::new(
361 "Boxing Day",
362 Self::observe_weekend(boxing),
363 0.02,
364 ));
365
366 cal
367 }
368
369 fn cn_holidays(year: i32) -> Self {
371 let mut cal = Self::new(Region::CN, year);
372
373 cal.add_holiday(Holiday::new(
375 "New Year",
376 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
377 0.05,
378 ));
379
380 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 cal.add_holiday(Holiday::new(
397 "Qingming Festival",
398 NaiveDate::from_ymd_opt(year, 4, 5).expect("valid date components"),
399 0.05,
400 ));
401
402 for i in 0..3 {
404 cal.add_holiday(Holiday::new(
405 if i == 0 {
406 "Labor Day"
407 } else {
408 "Labor Day Holiday"
409 },
410 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components")
411 + Duration::days(i),
412 0.05,
413 ));
414 }
415
416 cal.add_holiday(Holiday::new(
418 "Dragon Boat Festival",
419 NaiveDate::from_ymd_opt(year, 6, 10).expect("valid date components"),
420 0.05,
421 ));
422
423 cal.add_holiday(Holiday::new(
425 "Mid-Autumn Festival",
426 NaiveDate::from_ymd_opt(year, 9, 15).expect("valid date components"),
427 0.05,
428 ));
429
430 for i in 0..7 {
432 cal.add_holiday(Holiday::new(
433 if i == 0 {
434 "National Day"
435 } else {
436 "National Day Holiday"
437 },
438 NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components")
439 + Duration::days(i),
440 0.02,
441 ));
442 }
443
444 cal
445 }
446
447 fn jp_holidays(year: i32) -> Self {
449 let mut cal = Self::new(Region::JP, year);
450
451 cal.add_holiday(Holiday::new(
453 "Ganjitsu (New Year)",
454 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
455 0.02,
456 ));
457
458 cal.add_holiday(Holiday::new(
460 "New Year Holiday",
461 NaiveDate::from_ymd_opt(year, 1, 2).expect("valid date components"),
462 0.05,
463 ));
464 cal.add_holiday(Holiday::new(
465 "New Year Holiday",
466 NaiveDate::from_ymd_opt(year, 1, 3).expect("valid date components"),
467 0.05,
468 ));
469
470 let seijin = Self::nth_weekday_of_month(year, 1, Weekday::Mon, 2);
472 cal.add_holiday(Holiday::new("Seijin no Hi", seijin, 0.05));
473
474 cal.add_holiday(Holiday::new(
476 "Kenkoku Kinen no Hi",
477 NaiveDate::from_ymd_opt(year, 2, 11).expect("valid date components"),
478 0.02,
479 ));
480
481 cal.add_holiday(Holiday::new(
483 "Tenno Tanjobi",
484 NaiveDate::from_ymd_opt(year, 2, 23).expect("valid date components"),
485 0.02,
486 ));
487
488 cal.add_holiday(Holiday::new(
490 "Shunbun no Hi",
491 NaiveDate::from_ymd_opt(year, 3, 20).expect("valid date components"),
492 0.02,
493 ));
494
495 cal.add_holiday(Holiday::new(
497 "Showa no Hi",
498 NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
499 0.02,
500 ));
501
502 cal.add_holiday(Holiday::new(
504 "Kenpo Kinenbi",
505 NaiveDate::from_ymd_opt(year, 5, 3).expect("valid date components"),
506 0.02,
507 ));
508 cal.add_holiday(Holiday::new(
509 "Midori no Hi",
510 NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
511 0.02,
512 ));
513 cal.add_holiday(Holiday::new(
514 "Kodomo no Hi",
515 NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
516 0.02,
517 ));
518
519 let umi = Self::nth_weekday_of_month(year, 7, Weekday::Mon, 3);
521 cal.add_holiday(Holiday::new("Umi no Hi", umi, 0.05));
522
523 cal.add_holiday(Holiday::new(
525 "Yama no Hi",
526 NaiveDate::from_ymd_opt(year, 8, 11).expect("valid date components"),
527 0.05,
528 ));
529
530 let keiro = Self::nth_weekday_of_month(year, 9, Weekday::Mon, 3);
532 cal.add_holiday(Holiday::new("Keiro no Hi", keiro, 0.05));
533
534 cal.add_holiday(Holiday::new(
536 "Shubun no Hi",
537 NaiveDate::from_ymd_opt(year, 9, 23).expect("valid date components"),
538 0.02,
539 ));
540
541 let sports = Self::nth_weekday_of_month(year, 10, Weekday::Mon, 2);
543 cal.add_holiday(Holiday::new("Sports Day", sports, 0.05));
544
545 cal.add_holiday(Holiday::new(
547 "Bunka no Hi",
548 NaiveDate::from_ymd_opt(year, 11, 3).expect("valid date components"),
549 0.02,
550 ));
551
552 cal.add_holiday(Holiday::new(
554 "Kinro Kansha no Hi",
555 NaiveDate::from_ymd_opt(year, 11, 23).expect("valid date components"),
556 0.02,
557 ));
558
559 cal
560 }
561
562 fn in_holidays(year: i32) -> Self {
564 let mut cal = Self::new(Region::IN, year);
565
566 cal.add_holiday(Holiday::new(
568 "Republic Day",
569 NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components"),
570 0.02,
571 ));
572
573 cal.add_holiday(Holiday::new(
575 "Holi",
576 NaiveDate::from_ymd_opt(year, 3, 10).expect("valid date components"),
577 0.05,
578 ));
579
580 let easter = Self::easter_date(year);
582 cal.add_holiday(Holiday::new(
583 "Good Friday",
584 easter - Duration::days(2),
585 0.05,
586 ));
587
588 cal.add_holiday(Holiday::new(
590 "Independence Day",
591 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
592 0.02,
593 ));
594
595 cal.add_holiday(Holiday::new(
597 "Gandhi Jayanti",
598 NaiveDate::from_ymd_opt(year, 10, 2).expect("valid date components"),
599 0.02,
600 ));
601
602 cal.add_holiday(Holiday::new(
604 "Dussehra",
605 NaiveDate::from_ymd_opt(year, 10, 15).expect("valid date components"),
606 0.05,
607 ));
608
609 let diwali = Self::approximate_diwali(year);
611 for i in 0..5 {
612 cal.add_holiday(Holiday::new(
613 match i {
614 0 => "Dhanteras",
615 1 => "Naraka Chaturdashi",
616 2 => "Diwali",
617 3 => "Govardhan Puja",
618 _ => "Bhai Dooj",
619 },
620 diwali + Duration::days(i),
621 if i == 2 { 0.02 } else { 0.1 },
622 ));
623 }
624
625 cal.add_holiday(Holiday::new(
627 "Christmas",
628 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
629 0.1,
630 ));
631
632 cal
633 }
634
635 fn br_holidays(year: i32) -> Self {
637 let mut cal = Self::new(Region::BR, year);
638
639 cal.add_holiday(Holiday::new(
641 "Confraternização Universal",
642 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
643 0.02,
644 ));
645
646 let easter = Self::easter_date(year);
648 let carnival_tuesday = easter - Duration::days(47);
649 let carnival_monday = carnival_tuesday - Duration::days(1);
650 cal.add_holiday(Holiday::new("Carnaval (Segunda)", carnival_monday, 0.02));
651 cal.add_holiday(Holiday::new("Carnaval (Terça)", carnival_tuesday, 0.02));
652
653 cal.add_holiday(Holiday::new(
655 "Sexta-feira Santa",
656 easter - Duration::days(2),
657 0.02,
658 ));
659
660 cal.add_holiday(Holiday::new(
662 "Tiradentes",
663 NaiveDate::from_ymd_opt(year, 4, 21).expect("valid date components"),
664 0.02,
665 ));
666
667 cal.add_holiday(Holiday::new(
669 "Dia do Trabalho",
670 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
671 0.02,
672 ));
673
674 cal.add_holiday(Holiday::new(
676 "Corpus Christi",
677 easter + Duration::days(60),
678 0.05,
679 ));
680
681 cal.add_holiday(Holiday::new(
683 "IndependĂȘncia do Brasil",
684 NaiveDate::from_ymd_opt(year, 9, 7).expect("valid date components"),
685 0.02,
686 ));
687
688 cal.add_holiday(Holiday::new(
690 "Nossa Senhora Aparecida",
691 NaiveDate::from_ymd_opt(year, 10, 12).expect("valid date components"),
692 0.02,
693 ));
694
695 cal.add_holiday(Holiday::new(
697 "Finados",
698 NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
699 0.02,
700 ));
701
702 cal.add_holiday(Holiday::new(
704 "Proclamação da RepĂșblica",
705 NaiveDate::from_ymd_opt(year, 11, 15).expect("valid date components"),
706 0.02,
707 ));
708
709 cal.add_holiday(Holiday::new(
711 "Natal",
712 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
713 0.02,
714 ));
715
716 cal
717 }
718
719 fn mx_holidays(year: i32) -> Self {
721 let mut cal = Self::new(Region::MX, year);
722
723 cal.add_holiday(Holiday::new(
725 "Año Nuevo",
726 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
727 0.02,
728 ));
729
730 let constitution = Self::nth_weekday_of_month(year, 2, Weekday::Mon, 1);
732 cal.add_holiday(Holiday::new("DĂa de la ConstituciĂłn", constitution, 0.02));
733
734 let juarez = Self::nth_weekday_of_month(year, 3, Weekday::Mon, 3);
736 cal.add_holiday(Holiday::new("Natalicio de Benito JuĂĄrez", juarez, 0.02));
737
738 let easter = Self::easter_date(year);
740 cal.add_holiday(Holiday::new(
741 "Jueves Santo",
742 easter - Duration::days(3),
743 0.05,
744 ));
745 cal.add_holiday(Holiday::new(
746 "Viernes Santo",
747 easter - Duration::days(2),
748 0.02,
749 ));
750
751 cal.add_holiday(Holiday::new(
753 "DĂa del Trabajo",
754 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
755 0.02,
756 ));
757
758 cal.add_holiday(Holiday::new(
760 "DĂa de la Independencia",
761 NaiveDate::from_ymd_opt(year, 9, 16).expect("valid date components"),
762 0.02,
763 ));
764
765 let revolution = Self::nth_weekday_of_month(year, 11, Weekday::Mon, 3);
767 cal.add_holiday(Holiday::new("DĂa de la RevoluciĂłn", revolution, 0.02));
768
769 cal.add_holiday(Holiday::new(
771 "DĂa de Muertos",
772 NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
773 0.1,
774 ));
775 cal.add_holiday(Holiday::new(
776 "DĂa de Muertos",
777 NaiveDate::from_ymd_opt(year, 11, 2).expect("valid date components"),
778 0.1,
779 ));
780
781 cal.add_holiday(Holiday::new(
783 "Navidad",
784 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
785 0.02,
786 ));
787
788 cal
789 }
790
791 fn au_holidays(year: i32) -> Self {
793 let mut cal = Self::new(Region::AU, year);
794
795 let new_years = NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components");
797 cal.add_holiday(Holiday::new(
798 "New Year's Day",
799 Self::observe_weekend(new_years),
800 0.02,
801 ));
802
803 let australia_day = NaiveDate::from_ymd_opt(year, 1, 26).expect("valid date components");
805 cal.add_holiday(Holiday::new(
806 "Australia Day",
807 Self::observe_weekend(australia_day),
808 0.02,
809 ));
810
811 let easter = Self::easter_date(year);
813 cal.add_holiday(Holiday::new(
814 "Good Friday",
815 easter - Duration::days(2),
816 0.02,
817 ));
818
819 cal.add_holiday(Holiday::new(
821 "Easter Saturday",
822 easter - Duration::days(1),
823 0.02,
824 ));
825
826 cal.add_holiday(Holiday::new(
828 "Easter Monday",
829 easter + Duration::days(1),
830 0.02,
831 ));
832
833 let anzac = NaiveDate::from_ymd_opt(year, 4, 25).expect("valid date components");
835 cal.add_holiday(Holiday::new("ANZAC Day", anzac, 0.02));
836
837 let queens_birthday = Self::nth_weekday_of_month(year, 6, Weekday::Mon, 2);
839 cal.add_holiday(Holiday::new("Queen's Birthday", queens_birthday, 0.02));
840
841 let christmas = NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components");
843 cal.add_holiday(Holiday::new(
844 "Christmas Day",
845 Self::observe_weekend(christmas),
846 0.02,
847 ));
848
849 let boxing = NaiveDate::from_ymd_opt(year, 12, 26).expect("valid date components");
851 cal.add_holiday(Holiday::new(
852 "Boxing Day",
853 Self::observe_weekend(boxing),
854 0.02,
855 ));
856
857 cal
858 }
859
860 fn sg_holidays(year: i32) -> Self {
862 let mut cal = Self::new(Region::SG, year);
863
864 cal.add_holiday(Holiday::new(
866 "New Year's Day",
867 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
868 0.02,
869 ));
870
871 let cny = Self::approximate_chinese_new_year(year);
873 cal.add_holiday(Holiday::new("Chinese New Year", cny, 0.02));
874 cal.add_holiday(Holiday::new(
875 "Chinese New Year (Day 2)",
876 cny + Duration::days(1),
877 0.02,
878 ));
879
880 let easter = Self::easter_date(year);
882 cal.add_holiday(Holiday::new(
883 "Good Friday",
884 easter - Duration::days(2),
885 0.02,
886 ));
887
888 cal.add_holiday(Holiday::new(
890 "Labour Day",
891 NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
892 0.02,
893 ));
894
895 let vesak = Self::approximate_vesak(year);
897 cal.add_holiday(Holiday::new("Vesak Day", vesak, 0.02));
898
899 let hari_raya_puasa = Self::approximate_hari_raya_puasa(year);
901 cal.add_holiday(Holiday::new("Hari Raya Puasa", hari_raya_puasa, 0.02));
902
903 let hari_raya_haji = Self::approximate_hari_raya_haji(year);
905 cal.add_holiday(Holiday::new("Hari Raya Haji", hari_raya_haji, 0.02));
906
907 cal.add_holiday(Holiday::new(
909 "National Day",
910 NaiveDate::from_ymd_opt(year, 8, 9).expect("valid date components"),
911 0.02,
912 ));
913
914 let deepavali = Self::approximate_deepavali(year);
916 cal.add_holiday(Holiday::new("Deepavali", deepavali, 0.02));
917
918 cal.add_holiday(Holiday::new(
920 "Christmas Day",
921 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
922 0.02,
923 ));
924
925 cal
926 }
927
928 fn kr_holidays(year: i32) -> Self {
930 let mut cal = Self::new(Region::KR, year);
931
932 cal.add_holiday(Holiday::new(
934 "Sinjeong",
935 NaiveDate::from_ymd_opt(year, 1, 1).expect("valid date components"),
936 0.02,
937 ));
938
939 let seollal = Self::approximate_korean_new_year(year);
941 cal.add_holiday(Holiday::new(
942 "Seollal (Eve)",
943 seollal - Duration::days(1),
944 0.02,
945 ));
946 cal.add_holiday(Holiday::new("Seollal", seollal, 0.02));
947 cal.add_holiday(Holiday::new(
948 "Seollal (Day 2)",
949 seollal + Duration::days(1),
950 0.02,
951 ));
952
953 cal.add_holiday(Holiday::new(
955 "Samiljeol",
956 NaiveDate::from_ymd_opt(year, 3, 1).expect("valid date components"),
957 0.02,
958 ));
959
960 cal.add_holiday(Holiday::new(
962 "Eorininal",
963 NaiveDate::from_ymd_opt(year, 5, 5).expect("valid date components"),
964 0.02,
965 ));
966
967 let buddha_birthday = Self::approximate_korean_buddha_birthday(year);
969 cal.add_holiday(Holiday::new("Seokgatansinil", buddha_birthday, 0.02));
970
971 cal.add_holiday(Holiday::new(
973 "Hyeonchungil",
974 NaiveDate::from_ymd_opt(year, 6, 6).expect("valid date components"),
975 0.02,
976 ));
977
978 cal.add_holiday(Holiday::new(
980 "Gwangbokjeol",
981 NaiveDate::from_ymd_opt(year, 8, 15).expect("valid date components"),
982 0.02,
983 ));
984
985 let chuseok = Self::approximate_chuseok(year);
987 cal.add_holiday(Holiday::new(
988 "Chuseok (Eve)",
989 chuseok - Duration::days(1),
990 0.02,
991 ));
992 cal.add_holiday(Holiday::new("Chuseok", chuseok, 0.02));
993 cal.add_holiday(Holiday::new(
994 "Chuseok (Day 2)",
995 chuseok + Duration::days(1),
996 0.02,
997 ));
998
999 cal.add_holiday(Holiday::new(
1001 "Gaecheonjeol",
1002 NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1003 0.02,
1004 ));
1005
1006 cal.add_holiday(Holiday::new(
1008 "Hangullal",
1009 NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1010 0.02,
1011 ));
1012
1013 cal.add_holiday(Holiday::new(
1015 "Seongtanjeol",
1016 NaiveDate::from_ymd_opt(year, 12, 25).expect("valid date components"),
1017 0.02,
1018 ));
1019
1020 cal
1021 }
1022
1023 fn easter_date(year: i32) -> NaiveDate {
1025 let a = year % 19;
1026 let b = year / 100;
1027 let c = year % 100;
1028 let d = b / 4;
1029 let e = b % 4;
1030 let f = (b + 8) / 25;
1031 let g = (b - f + 1) / 3;
1032 let h = (19 * a + b - d - g + 15) % 30;
1033 let i = c / 4;
1034 let k = c % 4;
1035 let l = (32 + 2 * e + 2 * i - h - k) % 7;
1036 let m = (a + 11 * h + 22 * l) / 451;
1037 let month = (h + l - 7 * m + 114) / 31;
1038 let day = ((h + l - 7 * m + 114) % 31) + 1;
1039
1040 NaiveDate::from_ymd_opt(year, month as u32, day as u32).expect("valid date components")
1041 }
1042
1043 fn nth_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u32) -> NaiveDate {
1045 let first = NaiveDate::from_ymd_opt(year, month, 1).expect("valid date components");
1046 let first_weekday = first.weekday();
1047
1048 let days_until = (weekday.num_days_from_monday() as i64
1049 - first_weekday.num_days_from_monday() as i64
1050 + 7)
1051 % 7;
1052
1053 first + Duration::days(days_until + (n - 1) as i64 * 7)
1054 }
1055
1056 fn last_weekday_of_month(year: i32, month: u32, weekday: Weekday) -> NaiveDate {
1058 let last = if month == 12 {
1059 NaiveDate::from_ymd_opt(year + 1, 1, 1).expect("valid date components")
1060 - Duration::days(1)
1061 } else {
1062 NaiveDate::from_ymd_opt(year, month + 1, 1).expect("valid date components")
1063 - Duration::days(1)
1064 };
1065
1066 let last_weekday = last.weekday();
1067 let days_back = (last_weekday.num_days_from_monday() as i64
1068 - weekday.num_days_from_monday() as i64
1069 + 7)
1070 % 7;
1071
1072 last - Duration::days(days_back)
1073 }
1074
1075 fn observe_weekend(date: NaiveDate) -> NaiveDate {
1077 match date.weekday() {
1078 Weekday::Sat => date - Duration::days(1), Weekday::Sun => date + Duration::days(1), _ => date,
1081 }
1082 }
1083
1084 fn approximate_chinese_new_year(year: i32) -> NaiveDate {
1086 let base_year = 2000;
1089 let cny_2000 = NaiveDate::from_ymd_opt(2000, 2, 5).expect("valid date components");
1090
1091 let years_diff = year - base_year;
1092 let lunar_cycle = 29.5306; let days_offset = (years_diff as f64 * 12.0 * lunar_cycle) % 365.25;
1094
1095 let mut result = cny_2000 + Duration::days(days_offset as i64);
1096
1097 while result.month() > 2 || (result.month() == 2 && result.day() > 20) {
1099 result -= Duration::days(29);
1100 }
1101 while result.month() < 1 || (result.month() == 1 && result.day() < 21) {
1102 result += Duration::days(29);
1103 }
1104
1105 if result.year() != year {
1107 result = NaiveDate::from_ymd_opt(year, result.month(), result.day().min(28))
1108 .unwrap_or_else(|| {
1109 NaiveDate::from_ymd_opt(year, result.month(), 28)
1110 .expect("valid date components")
1111 });
1112 }
1113
1114 result
1115 }
1116
1117 fn approximate_diwali(year: i32) -> NaiveDate {
1119 match year % 4 {
1122 0 => NaiveDate::from_ymd_opt(year, 11, 1).expect("valid date components"),
1123 1 => NaiveDate::from_ymd_opt(year, 10, 24).expect("valid date components"),
1124 2 => NaiveDate::from_ymd_opt(year, 11, 12).expect("valid date components"),
1125 _ => NaiveDate::from_ymd_opt(year, 11, 4).expect("valid date components"),
1126 }
1127 }
1128
1129 fn approximate_vesak(year: i32) -> NaiveDate {
1132 let base = match year % 19 {
1135 0 => 18,
1136 1 => 7,
1137 2 => 26,
1138 3 => 15,
1139 4 => 5,
1140 5 => 24,
1141 6 => 13,
1142 7 => 2,
1143 8 => 22,
1144 9 => 11,
1145 10 => 30,
1146 11 => 19,
1147 12 => 8,
1148 13 => 27,
1149 14 => 17,
1150 15 => 6,
1151 16 => 25,
1152 17 => 14,
1153 _ => 3,
1154 };
1155 let month = if base > 20 { 4 } else { 5 };
1156 let day = if base > 20 { base - 10 } else { base };
1157 NaiveDate::from_ymd_opt(year, month, day.clamp(1, 28) as u32)
1158 .expect("valid date components")
1159 }
1160
1161 fn approximate_hari_raya_puasa(year: i32) -> NaiveDate {
1164 let base_year = 2024;
1167 let base_date = NaiveDate::from_ymd_opt(2024, 4, 10).expect("valid date components");
1168 let years_diff = year - base_year;
1169 let days_shift = (years_diff as f64 * -10.63) as i64;
1170 let mut result = base_date + Duration::days(days_shift);
1171
1172 while result.year() != year {
1174 if result.year() > year {
1175 result -= Duration::days(354); } else {
1177 result += Duration::days(354);
1178 }
1179 }
1180 result
1181 }
1182
1183 fn approximate_hari_raya_haji(year: i32) -> NaiveDate {
1186 Self::approximate_hari_raya_puasa(year) + Duration::days(70)
1187 }
1188
1189 fn approximate_deepavali(year: i32) -> NaiveDate {
1191 Self::approximate_diwali(year)
1192 }
1193
1194 fn approximate_korean_new_year(year: i32) -> NaiveDate {
1197 Self::approximate_chinese_new_year(year)
1198 }
1199
1200 fn approximate_korean_buddha_birthday(year: i32) -> NaiveDate {
1203 match year % 19 {
1205 0 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1206 1 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1207 2 => NaiveDate::from_ymd_opt(year, 5, 23).expect("valid date components"),
1208 3 => NaiveDate::from_ymd_opt(year, 5, 12).expect("valid date components"),
1209 4 => NaiveDate::from_ymd_opt(year, 5, 1).expect("valid date components"),
1210 5 => NaiveDate::from_ymd_opt(year, 5, 20).expect("valid date components"),
1211 6 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1212 7 => NaiveDate::from_ymd_opt(year, 4, 29).expect("valid date components"),
1213 8 => NaiveDate::from_ymd_opt(year, 5, 18).expect("valid date components"),
1214 9 => NaiveDate::from_ymd_opt(year, 5, 7).expect("valid date components"),
1215 10 => NaiveDate::from_ymd_opt(year, 5, 26).expect("valid date components"),
1216 11 => NaiveDate::from_ymd_opt(year, 5, 15).expect("valid date components"),
1217 12 => NaiveDate::from_ymd_opt(year, 5, 4).expect("valid date components"),
1218 13 => NaiveDate::from_ymd_opt(year, 5, 24).expect("valid date components"),
1219 14 => NaiveDate::from_ymd_opt(year, 5, 13).expect("valid date components"),
1220 15 => NaiveDate::from_ymd_opt(year, 5, 2).expect("valid date components"),
1221 16 => NaiveDate::from_ymd_opt(year, 5, 21).expect("valid date components"),
1222 17 => NaiveDate::from_ymd_opt(year, 5, 10).expect("valid date components"),
1223 _ => NaiveDate::from_ymd_opt(year, 4, 30).expect("valid date components"),
1224 }
1225 }
1226
1227 fn approximate_chuseok(year: i32) -> NaiveDate {
1230 match year % 19 {
1232 0 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1233 1 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1234 2 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1235 3 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1236 4 => NaiveDate::from_ymd_opt(year, 10, 3).expect("valid date components"),
1237 5 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1238 6 => NaiveDate::from_ymd_opt(year, 9, 11).expect("valid date components"),
1239 7 => NaiveDate::from_ymd_opt(year, 9, 30).expect("valid date components"),
1240 8 => NaiveDate::from_ymd_opt(year, 9, 19).expect("valid date components"),
1241 9 => NaiveDate::from_ymd_opt(year, 10, 9).expect("valid date components"),
1242 10 => NaiveDate::from_ymd_opt(year, 9, 28).expect("valid date components"),
1243 11 => NaiveDate::from_ymd_opt(year, 9, 17).expect("valid date components"),
1244 12 => NaiveDate::from_ymd_opt(year, 10, 6).expect("valid date components"),
1245 13 => NaiveDate::from_ymd_opt(year, 9, 25).expect("valid date components"),
1246 14 => NaiveDate::from_ymd_opt(year, 9, 14).expect("valid date components"),
1247 15 => NaiveDate::from_ymd_opt(year, 10, 4).expect("valid date components"),
1248 16 => NaiveDate::from_ymd_opt(year, 9, 22).expect("valid date components"),
1249 17 => NaiveDate::from_ymd_opt(year, 9, 12).expect("valid date components"),
1250 _ => NaiveDate::from_ymd_opt(year, 10, 1).expect("valid date components"),
1251 }
1252 }
1253}
1254
1255#[derive(Debug, Clone, Serialize, Deserialize)]
1257pub struct CustomHolidayConfig {
1258 pub name: String,
1260 pub month: u8,
1262 pub day: u8,
1264 #[serde(default = "default_holiday_multiplier")]
1266 pub activity_multiplier: f64,
1267}
1268
1269fn default_holiday_multiplier() -> f64 {
1270 0.05
1271}
1272
1273impl CustomHolidayConfig {
1274 pub fn to_holiday(&self, year: i32) -> Holiday {
1276 Holiday::new(
1277 &self.name,
1278 NaiveDate::from_ymd_opt(year, self.month as u32, self.day as u32)
1279 .expect("valid date components"),
1280 self.activity_multiplier,
1281 )
1282 }
1283}
1284
1285#[cfg(test)]
1286#[allow(clippy::unwrap_used)]
1287mod tests {
1288 use super::*;
1289
1290 #[test]
1291 fn test_us_holidays() {
1292 let cal = HolidayCalendar::for_region(Region::US, 2024);
1293
1294 let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1296 assert!(cal.is_holiday(christmas));
1297
1298 let independence = NaiveDate::from_ymd_opt(2024, 7, 4).unwrap();
1300 assert!(cal.is_holiday(independence));
1301 }
1302
1303 #[test]
1304 fn test_german_holidays() {
1305 let cal = HolidayCalendar::for_region(Region::DE, 2024);
1306
1307 let unity = NaiveDate::from_ymd_opt(2024, 10, 3).unwrap();
1309 assert!(cal.is_holiday(unity));
1310 }
1311
1312 #[test]
1313 fn test_easter_calculation() {
1314 assert_eq!(
1316 HolidayCalendar::easter_date(2024),
1317 NaiveDate::from_ymd_opt(2024, 3, 31).unwrap()
1318 );
1319 assert_eq!(
1320 HolidayCalendar::easter_date(2025),
1321 NaiveDate::from_ymd_opt(2025, 4, 20).unwrap()
1322 );
1323 }
1324
1325 #[test]
1326 fn test_nth_weekday() {
1327 let mlk = HolidayCalendar::nth_weekday_of_month(2024, 1, Weekday::Mon, 3);
1329 assert_eq!(mlk, NaiveDate::from_ymd_opt(2024, 1, 15).unwrap());
1330
1331 let thanksgiving = HolidayCalendar::nth_weekday_of_month(2024, 11, Weekday::Thu, 4);
1333 assert_eq!(thanksgiving, NaiveDate::from_ymd_opt(2024, 11, 28).unwrap());
1334 }
1335
1336 #[test]
1337 fn test_last_weekday() {
1338 let memorial = HolidayCalendar::last_weekday_of_month(2024, 5, Weekday::Mon);
1340 assert_eq!(memorial, NaiveDate::from_ymd_opt(2024, 5, 27).unwrap());
1341 }
1342
1343 #[test]
1344 fn test_activity_multiplier() {
1345 let cal = HolidayCalendar::for_region(Region::US, 2024);
1346
1347 let christmas = NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1349 assert!(cal.get_multiplier(christmas) < 0.1);
1350
1351 let regular = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
1353 assert!((cal.get_multiplier(regular) - 1.0).abs() < 0.01);
1354 }
1355
1356 #[test]
1357 fn test_all_regions_have_holidays() {
1358 let regions = [
1359 Region::US,
1360 Region::DE,
1361 Region::GB,
1362 Region::CN,
1363 Region::JP,
1364 Region::IN,
1365 Region::BR,
1366 Region::MX,
1367 Region::AU,
1368 Region::SG,
1369 Region::KR,
1370 ];
1371
1372 for region in regions {
1373 let cal = HolidayCalendar::for_region(region, 2024);
1374 assert!(
1375 !cal.holidays.is_empty(),
1376 "Region {:?} should have holidays",
1377 region
1378 );
1379 }
1380 }
1381
1382 #[test]
1383 fn test_brazilian_holidays() {
1384 let cal = HolidayCalendar::for_region(Region::BR, 2024);
1385
1386 let independence = NaiveDate::from_ymd_opt(2024, 9, 7).unwrap();
1388 assert!(cal.is_holiday(independence));
1389
1390 let tiradentes = NaiveDate::from_ymd_opt(2024, 4, 21).unwrap();
1392 assert!(cal.is_holiday(tiradentes));
1393 }
1394
1395 #[test]
1396 fn test_mexican_holidays() {
1397 let cal = HolidayCalendar::for_region(Region::MX, 2024);
1398
1399 let independence = NaiveDate::from_ymd_opt(2024, 9, 16).unwrap();
1401 assert!(cal.is_holiday(independence));
1402 }
1403
1404 #[test]
1405 fn test_australian_holidays() {
1406 let cal = HolidayCalendar::for_region(Region::AU, 2024);
1407
1408 let anzac = NaiveDate::from_ymd_opt(2024, 4, 25).unwrap();
1410 assert!(cal.is_holiday(anzac));
1411
1412 let australia_day = NaiveDate::from_ymd_opt(2024, 1, 26).unwrap();
1414 assert!(cal.is_holiday(australia_day));
1415 }
1416
1417 #[test]
1418 fn test_singapore_holidays() {
1419 let cal = HolidayCalendar::for_region(Region::SG, 2024);
1420
1421 let national = NaiveDate::from_ymd_opt(2024, 8, 9).unwrap();
1423 assert!(cal.is_holiday(national));
1424 }
1425
1426 #[test]
1427 fn test_korean_holidays() {
1428 let cal = HolidayCalendar::for_region(Region::KR, 2024);
1429
1430 let liberation = NaiveDate::from_ymd_opt(2024, 8, 15).unwrap();
1432 assert!(cal.is_holiday(liberation));
1433
1434 let hangul = NaiveDate::from_ymd_opt(2024, 10, 9).unwrap();
1436 assert!(cal.is_holiday(hangul));
1437 }
1438
1439 #[test]
1440 fn test_chinese_holidays() {
1441 let cal = HolidayCalendar::for_region(Region::CN, 2024);
1442
1443 let national = NaiveDate::from_ymd_opt(2024, 10, 1).unwrap();
1445 assert!(cal.is_holiday(national));
1446 }
1447
1448 #[test]
1449 fn test_japanese_golden_week() {
1450 let cal = HolidayCalendar::for_region(Region::JP, 2024);
1451
1452 let kodomo = NaiveDate::from_ymd_opt(2024, 5, 5).unwrap();
1454 assert!(cal.is_holiday(kodomo));
1455 }
1456}