1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! A crate for finding out dates of Czech public holidays.

use chrono::{Datelike, NaiveDate};
use computus;
use std::i32::MAX as I32_MAX;

/// Get dates of Czech public holidays in a given year.
pub fn holidays_in_year(y: i32) -> Vec<NaiveDate> {
    let mut hiy: Vec<NaiveDate> = vec![];
    for (sh_sy, sh_ty, sh_m, sh_d) in STATIC_HOLIDAYS {
        if y >= *sh_sy && y <= *sh_ty {
            hiy.push(NaiveDate::from_ymd(y, *sh_m, *sh_d));
        }
    }
    hiy.append(&mut get_easter_holidays(y));
    hiy.sort();
    hiy
}

/// Get dates of Czech public holidays within a given from-to date
/// range. The range is inclusive.
pub fn holidays_within(from: &NaiveDate, to: &NaiveDate) -> Vec<NaiveDate> {
    let mut hir = vec![];
    for y in from.year()..to.year() + 1 {
        hir.append(&mut holidays_in_year(y))
    }
    hir.retain(|holiday| holiday >= from && holiday <= to);
    hir
}

/// Query whether a given date is a Czech public holiday.
pub fn is_holiday(date: &NaiveDate) -> bool {
    is_static_holiday(date) || is_easter_holiday(date)
}

/// Query whether a given date (year, month, day) is a Czech public
/// holiday.
pub fn is_holiday_ymd(y: i32, m: u32, d: u32) -> bool {
    is_holiday(&NaiveDate::from_ymd(y, m, d))
}

// [(since_year, to_year, month, day), (since_year, to_year, month, day), ...]
const STATIC_HOLIDAYS: &'static [(i32, i32, u32, u32)] = &[
    (1951, I32_MAX, 1, 1),
    (1951, I32_MAX, 5, 1),
    (1992, I32_MAX, 5, 8),
    (1990, I32_MAX, 7, 5),
    (1990, I32_MAX, 7, 6),
    (2000, I32_MAX, 9, 28),
    (1988, I32_MAX, 10, 28),
    (2000, I32_MAX, 11, 17),
    (1990, I32_MAX, 12, 24),
    (1951, I32_MAX, 12, 25),
    (1951, I32_MAX, 12, 26),
    (1947, 1991, 5, 9),
];

fn is_static_holiday(date: &NaiveDate) -> bool {
    let (y, m, d) = (date.year(), date.month(), date.day());
    for (sh_sy, sh_ty, sh_m, sh_d) in STATIC_HOLIDAYS {
        if y >= *sh_sy && y <= *sh_ty && m == *sh_m && d == *sh_d {
            return true;
        }
    }
    false
}

fn is_easter_holiday(date: &NaiveDate) -> bool {
    get_easter_holidays(date.year()).contains(date)
}

fn get_easter_holidays(year: i32) -> Vec<NaiveDate> {
    vec![
        get_easter_holiday_friday(year),
        get_easter_holiday_monday(year),
    ]
    .into_iter()
    .filter(|opt| opt.is_some())
    .map(|opt| opt.expect("Date is None even though guarded by is_some filter"))
    .collect()
}

fn get_easter_holiday_monday(year: i32) -> Option<NaiveDate> {
    if year >= 1951 {
        get_easter_sunday(year).map(|es| es.succ())
    } else {
        None
    }
}

fn get_easter_holiday_friday(year: i32) -> Option<NaiveDate> {
    if year >= 2016 {
        get_easter_sunday(year).map(|es| es.pred().pred())
    } else {
        None
    }
}

fn get_easter_sunday(year: i32) -> Option<NaiveDate> {
    let easter_sunday = computus::gregorian(year);
    match easter_sunday {
        Ok(es) => Some(NaiveDate::from_ymd(es.year, es.month, es.day)),
        Err(_) => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_holiday_ymd() {
        assert!(!is_holiday_ymd(1918, 10, 28));
        assert!(is_holiday_ymd(2018, 10, 28));

        assert!(!is_holiday_ymd(1999, 9, 28));
        assert!(is_holiday_ymd(2000, 9, 28));
        assert!(is_holiday_ymd(3000, 9, 28));

        assert!(is_holiday_ymd(2018, 4, 2));
        assert!(is_holiday_ymd(2018, 3, 30));

        assert!(is_holiday_ymd(2018, 4, 2));
        assert!(is_holiday_ymd(2018, 3, 30));

        assert!(is_holiday_ymd(2015, 4, 6));
        assert!(!is_holiday_ymd(2015, 4, 3));
    }

    #[test]
    fn test_holidays_within() {
        let from = NaiveDate::from_ymd(2015, 12, 1);
        let to = NaiveDate::from_ymd(2017, 4, 16);

        assert_eq!(
            holidays_within(&from, &to).as_slice(),
            &[
                NaiveDate::from_ymd(2015, 12, 24),
                NaiveDate::from_ymd(2015, 12, 25),
                NaiveDate::from_ymd(2015, 12, 26),
                NaiveDate::from_ymd(2016, 1, 1),
                NaiveDate::from_ymd(2016, 3, 25),
                NaiveDate::from_ymd(2016, 3, 28),
                NaiveDate::from_ymd(2016, 5, 1),
                NaiveDate::from_ymd(2016, 5, 8),
                NaiveDate::from_ymd(2016, 7, 5),
                NaiveDate::from_ymd(2016, 7, 6),
                NaiveDate::from_ymd(2016, 9, 28),
                NaiveDate::from_ymd(2016, 10, 28),
                NaiveDate::from_ymd(2016, 11, 17),
                NaiveDate::from_ymd(2016, 12, 24),
                NaiveDate::from_ymd(2016, 12, 25),
                NaiveDate::from_ymd(2016, 12, 26),
                NaiveDate::from_ymd(2017, 1, 1),
                NaiveDate::from_ymd(2017, 4, 14),
            ]
        );
    }
}