1use serde::{Deserialize, Serialize};
12use thiserror::Error;
13use time::{Date, Duration, Weekday};
14
15use std::collections::BTreeSet;
16
17mod calendar_definitions;
18
19pub use calendar_definitions::*;
20
21#[derive(Error, Debug)]
22pub enum CalendarError {
24 #[error("calendar could not been found")]
25 CalendarNotFound,
26 #[error("failed to create invalid date")]
27 OutOfBound(#[from] time::error::ComponentRange),
28 #[error("try to proceed beyond max date")]
29 MaxDay,
30 #[error("try to proceed before min date")]
31 MinDay,
32}
33
34type Result<T> = std::result::Result<T, CalendarError>;
35
36#[derive(Deserialize, Serialize, Debug, PartialEq)]
38pub enum NthWeek {
39 First,
40 Second,
41 Third,
42 Fourth,
43 Last,
44 SecondLast,
45 ThirdLast,
46 FourthLast,
47}
48
49#[derive(Deserialize, Serialize, Debug, PartialEq)]
50pub enum Holiday {
51 WeekDay(Weekday),
54 YearlyDay {
57 month: u8,
58 day: u8,
59 first: Option<i32>,
60 last: Option<i32>,
61 },
62 MovableYearlyDay {
68 month: u8,
69 day: u8,
70 first: Option<i32>,
71 last: Option<i32>,
72 },
73 ModifiedMovableYearlyDay {
77 month: u8,
78 day: u8,
79 first: Option<i32>,
80 last: Option<i32>,
81 },
82 SingularDay(Date),
84 EasterOffset {
86 offset: i32,
87 first: Option<i32>,
88 last: Option<i32>,
89 },
90 MonthWeekday {
93 month: u8,
94 weekday: Weekday,
95 nth: NthWeek,
96 first: Option<i32>,
97 last: Option<i32>,
98 },
99}
100
101#[derive(Debug, Clone)]
103pub struct Calendar {
104 holidays: BTreeSet<Date>,
105 weekdays: Vec<Weekday>,
106}
107
108fn new_date(year: i32, month: u8, day: u8) -> Result<Date> {
109 Ok(Date::from_calendar_date(year, month.try_into()?, day)?)
110}
111
112impl Calendar {
113 pub fn calc_calendar(holiday_rules: &[Holiday], start: i32, end: i32) -> Result<Calendar> {
117 let mut holidays = BTreeSet::new();
118 let mut weekdays = Vec::new();
119
120 for rule in holiday_rules {
121 match rule {
122 Holiday::SingularDay(date) => {
123 let year = date.year();
124 if year >= start && year <= end {
125 holidays.insert(*date);
126 }
127 }
128 Holiday::WeekDay(weekday) => {
129 weekdays.push(*weekday);
130 }
131 Holiday::YearlyDay {
132 month,
133 day,
134 first,
135 last,
136 } => {
137 let (first, last) = Self::calc_first_and_last(start, end, first, last);
138 for year in first..last + 1 {
139 holidays.insert(new_date(year, *month, *day)?);
140 }
141 }
142 Holiday::MovableYearlyDay {
143 month,
144 day,
145 first,
146 last,
147 } => {
148 let (first, last) = Self::calc_first_and_last(start, end, first, last);
149 for year in first..last + 1 {
150 let date = new_date(year, *month, *day)?;
151 let mut date = match date.weekday() {
152 Weekday::Saturday => date
153 .next_day()
154 .ok_or(CalendarError::MaxDay)?
155 .next_day()
156 .ok_or(CalendarError::MaxDay)?,
157 Weekday::Sunday => date.next_day().ok_or(CalendarError::MaxDay)?,
158 _ => date,
159 };
160 while holidays.contains(&date) {
161 date = date.next_day().ok_or(CalendarError::MaxDay)?;
162 }
163 holidays.insert(date);
164 }
165 }
166 Holiday::ModifiedMovableYearlyDay {
167 month,
168 day,
169 first,
170 last,
171 } => {
172 let (first, last) = Self::calc_first_and_last(start, end, first, last);
173 for year in first..last + 1 {
174 let date = new_date(year, *month, *day)?;
175 let moved_date = match date.weekday() {
176 Weekday::Saturday => {
177 date.previous_day().ok_or(CalendarError::MinDay)?
178 }
179 Weekday::Sunday => date.next_day().ok_or(CalendarError::MaxDay)?,
180 _ => date,
181 };
182 if moved_date.month() == date.month() {
183 holidays.insert(moved_date);
184 } else {
185 holidays.insert(date);
186 }
187 }
188 }
189 Holiday::EasterOffset {
190 offset,
191 first,
192 last,
193 } => {
194 let (first, last) = Self::calc_first_and_last(start, end, first, last);
195 for year in first..last + 1 {
196 let easter = computus::gregorian(year).unwrap();
197 let easter = new_date(easter.year, easter.month as u8, easter.day as u8)?;
198 let date = easter.checked_add(Duration::days(*offset as i64)).unwrap();
199 holidays.insert(date);
200 }
201 }
202 Holiday::MonthWeekday {
203 month,
204 weekday,
205 nth,
206 first,
207 last,
208 } => {
209 let (first, last) = Self::calc_first_and_last(start, end, first, last);
210 for year in first..last + 1 {
211 let day = match nth {
212 NthWeek::First => 1,
213 NthWeek::Second => 8,
214 NthWeek::Third => 15,
215 NthWeek::Fourth => 22,
216 NthWeek::Last => last_day_of_month(year, *month),
217 NthWeek::SecondLast => last_day_of_month(year, *month) - 7,
218 NthWeek::ThirdLast => last_day_of_month(year, *month) - 14,
219 NthWeek::FourthLast => last_day_of_month(year, *month) - 21,
220 };
221 let mut date = new_date(year, *month, day)?;
222 while date.weekday() != *weekday {
223 date = match nth {
224 NthWeek::Last
225 | NthWeek::SecondLast
226 | NthWeek::ThirdLast
227 | NthWeek::FourthLast => {
228 date.previous_day().ok_or(CalendarError::MinDay)?
229 }
230 _ => date.next_day().ok_or(CalendarError::MaxDay)?,
231 }
232 }
233 holidays.insert(date);
234 }
235 }
236 }
237 }
238 Ok(Calendar { holidays, weekdays })
239 }
240
241 pub fn next_bday(&self, date: Date) -> Result<Date> {
243 let mut date = date.next_day().ok_or(CalendarError::MaxDay)?;
244 while !self.is_business_day(date) {
245 date = date.next_day().ok_or(CalendarError::MaxDay)?;
246 }
247 Ok(date)
248 }
249
250 pub fn prev_bday(&self, date: Date) -> Result<Date> {
252 let mut date = date.previous_day().ok_or(CalendarError::MinDay)?;
253 while !self.is_business_day(date) {
254 date = date.previous_day().ok_or(CalendarError::MinDay)?;
255 }
256 Ok(date)
257 }
258
259 fn calc_first_and_last(
260 start: i32,
261 end: i32,
262 first: &Option<i32>,
263 last: &Option<i32>,
264 ) -> (i32, i32) {
265 let first = match first {
266 Some(year) => std::cmp::max(start, *year),
267 _ => start,
268 };
269 let last = match last {
270 Some(year) => std::cmp::min(end, *year),
271 _ => end,
272 };
273 (first, last)
274 }
275
276 pub fn is_weekend(&self, day: Date) -> bool {
278 let weekday = day.weekday();
279 for w_day in &self.weekdays {
280 if weekday == *w_day {
281 return true;
282 }
283 }
284 false
285 }
286
287 pub fn is_holiday(&self, date: Date) -> bool {
289 self.holidays.contains(&date)
290 }
291
292 pub fn is_business_day(&self, date: Date) -> bool {
294 !self.is_weekend(date) && !self.is_holiday(date)
295 }
296}
297
298pub trait CalendarProvider {
299 fn get_calendar(&self, calendar_name: &str) -> Result<&Calendar>;
300}
301
302pub fn is_leap_year(year: i32) -> bool {
304 new_date(year, 2, 29).is_ok()
305}
306
307pub fn last_day_of_month(year: i32, month: u8) -> u8 {
309 if let Ok(date) = new_date(year, month + 1, 1) {
310 date.previous_day().unwrap().day()
311 } else {
312 31
314 }
315}
316
317pub struct SimpleCalendar {
318 cal: Calendar,
319}
320
321impl SimpleCalendar {
322 pub fn new(cal: &Calendar) -> SimpleCalendar {
323 SimpleCalendar { cal: cal.clone() }
324 }
325}
326
327impl CalendarProvider for SimpleCalendar {
328 fn get_calendar(&self, _calendar_name: &str) -> Result<&Calendar> {
329 Ok(&self.cal)
330 }
331}
332
333impl Default for SimpleCalendar {
334 fn default() -> SimpleCalendar {
335 SimpleCalendar {
336 cal: Calendar::calc_calendar(&[], 2020, 2021).unwrap(),
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use time::macros::date;
345
346 #[test]
347 fn fixed_dates_calendar() {
348 let holidays = vec![
349 Holiday::SingularDay(date!(2019 - 11 - 20)),
350 Holiday::SingularDay(date!(2019 - 11 - 24)),
351 Holiday::SingularDay(date!(2019 - 11 - 25)),
352 Holiday::WeekDay(Weekday::Saturday),
353 Holiday::WeekDay(Weekday::Sunday),
354 ];
355 let cal = Calendar::calc_calendar(&holidays, 2019, 2019).unwrap();
356
357 assert_eq!(false, cal.is_business_day(date!(2019 - 11 - 20)));
358 assert_eq!(true, cal.is_business_day(date!(2019 - 11 - 21)));
359 assert_eq!(true, cal.is_business_day(date!(2019 - 11 - 22)));
360 assert_eq!(false, cal.is_business_day(date!(2019 - 11 - 23)));
362 assert_eq!(true, cal.is_weekend(date!(2019 - 11 - 23)));
363 assert_eq!(false, cal.is_holiday(date!(2019 - 11 - 23)));
364 assert_eq!(false, cal.is_business_day(date!(2019 - 11 - 24)));
366 assert_eq!(true, cal.is_weekend(date!(2019 - 11 - 24)));
367 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 24)));
368 assert_eq!(false, cal.is_business_day(date!(2019 - 11 - 25)));
369 assert_eq!(true, cal.is_business_day(date!(2019 - 11 - 26)));
370 }
371
372 #[test]
373 fn test_yearly_day() {
374 let holidays = vec![
375 Holiday::YearlyDay {
376 month: 11,
377 day: 1,
378 first: None,
379 last: None,
380 },
381 Holiday::YearlyDay {
382 month: 11,
383 day: 2,
384 first: Some(2019),
385 last: None,
386 },
387 Holiday::YearlyDay {
388 month: 11,
389 day: 3,
390 first: None,
391 last: Some(2019),
392 },
393 Holiday::YearlyDay {
394 month: 11,
395 day: 4,
396 first: Some(2019),
397 last: Some(2019),
398 },
399 ];
400 let cal = Calendar::calc_calendar(&holidays, 2018, 2020).unwrap();
401
402 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 1)));
403 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 1)));
404 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 1)));
405
406 assert_eq!(false, cal.is_holiday(date!(2018 - 11 - 2)));
407 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 2)));
408 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 2)));
409
410 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 3)));
411 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 3)));
412 assert_eq!(false, cal.is_holiday(date!(2020 - 11 - 3)));
413
414 assert_eq!(false, cal.is_holiday(date!(2018 - 11 - 4)));
415 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 4)));
416 assert_eq!(false, cal.is_holiday(date!(2020 - 11 - 4)));
417 }
418
419 #[test]
420 fn test_movable_yearly_day() {
421 let holidays = vec![
422 Holiday::MovableYearlyDay {
423 month: 11,
424 day: 1,
425 first: None,
426 last: None,
427 },
428 Holiday::MovableYearlyDay {
429 month: 11,
430 day: 2,
431 first: None,
432 last: None,
433 },
434 Holiday::MovableYearlyDay {
435 month: 11,
436 day: 10,
437 first: None,
438 last: Some(2019),
439 },
440 Holiday::MovableYearlyDay {
441 month: 11,
442 day: 17,
443 first: Some(2019),
444 last: None,
445 },
446 Holiday::MovableYearlyDay {
447 month: 11,
448 day: 24,
449 first: Some(2019),
450 last: Some(2019),
451 },
452 ];
453 let cal = Calendar::calc_calendar(&holidays, 2018, 2020).unwrap();
454 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 1)));
455 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 2)));
456 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 1)));
457 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 4)));
458 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 2)));
459 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 3)));
460
461 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 12)));
462 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 11)));
463 assert_eq!(false, cal.is_holiday(date!(2020 - 11 - 10)));
464 assert_eq!(false, cal.is_holiday(date!(2018 - 11 - 19)));
465 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 18)));
466 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 17)));
467 assert_eq!(false, cal.is_holiday(date!(2018 - 11 - 26)));
468 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 25)));
469 assert_eq!(false, cal.is_holiday(date!(2020 - 11 - 24)));
470 }
471
472 #[test]
473 fn test_easter_offset() {
475 let holidays = vec![Holiday::EasterOffset {
476 offset: -2,
477 first: None,
478 last: None,
479 }];
480 let cal = Calendar::calc_calendar(&holidays, 2019, 2020).unwrap();
481 assert_eq!(false, cal.is_business_day(date!(2019 - 4 - 19)));
482 assert_eq!(false, cal.is_business_day(date!(2020 - 4 - 10)));
483 }
484
485 #[test]
486 fn test_month_weekday() {
487 let holidays = vec![
488 Holiday::MonthWeekday {
489 month: 11,
490 weekday: Weekday::Monday,
491 nth: NthWeek::First,
492 first: None,
493 last: None,
494 },
495 Holiday::MonthWeekday {
496 month: 11,
497 weekday: Weekday::Tuesday,
498 nth: NthWeek::Second,
499 first: None,
500 last: None,
501 },
502 Holiday::MonthWeekday {
503 month: 11,
504 weekday: Weekday::Wednesday,
505 nth: NthWeek::Third,
506 first: None,
507 last: None,
508 },
509 Holiday::MonthWeekday {
510 month: 11,
511 weekday: Weekday::Thursday,
512 nth: NthWeek::Fourth,
513 first: None,
514 last: None,
515 },
516 Holiday::MonthWeekday {
517 month: 11,
518 weekday: Weekday::Friday,
519 nth: NthWeek::Last,
520 first: None,
521 last: None,
522 },
523 Holiday::MonthWeekday {
524 month: 11,
525 weekday: Weekday::Saturday,
526 nth: NthWeek::First,
527 first: None,
528 last: Some(2018),
529 },
530 Holiday::MonthWeekday {
531 month: 11,
532 weekday: Weekday::Sunday,
533 nth: NthWeek::Last,
534 first: Some(2020),
535 last: None,
536 },
537 ];
538 let cal = Calendar::calc_calendar(&holidays, 2018, 2020).unwrap();
539 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 4)));
540 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 12)));
541 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 20)));
542 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 28)));
543 assert_eq!(true, cal.is_holiday(date!(2019 - 11 - 29)));
544
545 assert_eq!(true, cal.is_holiday(date!(2018 - 11 - 3)));
546 assert_eq!(false, cal.is_holiday(date!(2019 - 11 - 2)));
547 assert_eq!(false, cal.is_holiday(date!(2020 - 11 - 7)));
548 assert_eq!(false, cal.is_holiday(date!(2018 - 11 - 25)));
549 assert_eq!(false, cal.is_holiday(date!(2019 - 11 - 24)));
550 assert_eq!(true, cal.is_holiday(date!(2020 - 11 - 29)));
551 }
552
553 #[test]
554 fn serialize_cal_definition() {
556 let holidays = vec![
557 Holiday::MonthWeekday {
558 month: 11,
559 weekday: Weekday::Monday,
560 nth: NthWeek::First,
561 first: None,
562 last: None,
563 },
564 Holiday::MovableYearlyDay {
565 month: 11,
566 day: 1,
567 first: Some(2016),
568 last: None,
569 },
570 Holiday::YearlyDay {
571 month: 11,
572 day: 3,
573 first: None,
574 last: Some(2019),
575 },
576 Holiday::SingularDay(date!(2019 - 11 - 25)),
577 Holiday::WeekDay(Weekday::Saturday),
578 Holiday::EasterOffset {
579 offset: -2,
580 first: None,
581 last: None,
582 },
583 ];
584 let json = serde_json::to_string_pretty(&holidays).unwrap();
585 assert_eq!(
586 json,
587 r#"[
588 {
589 "MonthWeekday": {
590 "month": 11,
591 "weekday": 1,
592 "nth": "First",
593 "first": null,
594 "last": null
595 }
596 },
597 {
598 "MovableYearlyDay": {
599 "month": 11,
600 "day": 1,
601 "first": 2016,
602 "last": null
603 }
604 },
605 {
606 "YearlyDay": {
607 "month": 11,
608 "day": 3,
609 "first": null,
610 "last": 2019
611 }
612 },
613 {
614 "SingularDay": [
615 2019,
616 329
617 ]
618 },
619 {
620 "WeekDay": 6
621 },
622 {
623 "EasterOffset": {
624 "offset": -2,
625 "first": null,
626 "last": null
627 }
628 }
629]"#
630 );
631 let holidays2: Vec<Holiday> = serde_json::from_str(&json).unwrap();
632 assert_eq!(holidays.len(), holidays2.len());
633 for i in 0..holidays.len() {
634 assert_eq!(holidays[i], holidays2[i]);
635 }
636 }
637
638 #[test]
639 fn test_modified_movable() {
640 let holidays = vec![
641 Holiday::ModifiedMovableYearlyDay {
642 month: 12,
643 day: 25,
644 first: None,
645 last: None,
646 },
647 Holiday::ModifiedMovableYearlyDay {
648 month: 1,
649 day: 1,
650 first: None,
651 last: None,
652 },
653 ];
654 let cal = Calendar::calc_calendar(&holidays, 2020, 2023).unwrap();
655 assert!(cal.is_holiday(date!(2020 - 12 - 25)));
656 assert!(cal.is_holiday(date!(2021 - 12 - 24)));
657 assert!(cal.is_holiday(date!(2022 - 12 - 26)));
658 assert!(cal.is_holiday(date!(2023 - 12 - 25)));
659 assert!(cal.is_holiday(date!(2020 - 1 - 1)));
660 assert!(cal.is_holiday(date!(2021 - 1 - 1)));
661 assert!(cal.is_holiday(date!(2022 - 1 - 1)));
662 assert!(cal.is_holiday(date!(2023 - 1 - 2)));
663 }
664}