holiday_cn/
lib.rs

1use time::{format_description, Date, Error, UtcOffset, Weekday};
2
3include!(concat!(env!("OUT_DIR"), "/holiday_data.rs"));
4
5/// Check if a given date is an off-day (holiday or weekend)
6///
7/// # Arguments
8///
9/// * `date` - Date string in YYYY-MM-DD format
10///
11/// # Returns
12///
13/// * `Ok((bool, Option<&'static str>))` - A tuple containing:
14///   * boolean indicating if it's an off-day (holiday or weekend)
15///   * Optional holiday name if it exists
16/// * `Err` - If the date string is invalid
17pub fn is_offday(date: &str) -> Result<(bool, Option<&'static str>), Error> {
18    let format = format_description::parse("[year]-[month]-[day]")?;
19    let date = Date::parse(date, &format)?;
20    let year = date.year();
21    let weekday = date.weekday();
22
23    if let Some(year_data) = get_year_data(year) {
24        if let Some((name, is_off_day)) = year_data.get(&date.format(&format).unwrap()) {
25            return Ok((*is_off_day, Some(name)));
26        }
27    }
28
29    // If not in holiday data, check if it's a weekend
30    Ok((matches!(weekday, Weekday::Saturday | Weekday::Sunday), None))
31}
32
33/// Check if a given date is a workday
34///
35/// # Arguments
36///
37/// * `date` - Date string in YYYY-MM-DD format
38///
39/// # Returns
40///
41/// * `Ok(bool)` - true if it's a workday, false otherwise
42/// * `Err` - If the date string is invalid
43pub fn is_workday(date: &str) -> Result<bool, Error> {
44    let (is_off, _) = is_offday(date)?;
45    Ok(!is_off)
46}
47
48/// Check if current time (in UTC+8) is an off-day
49///
50/// # Returns
51///
52/// * `(bool, Option<&'static str>)` - A tuple containing:
53///   * boolean indicating if it's an off-day (holiday or weekend)
54///   * Optional holiday name if it exists
55pub fn is_now_offday() -> (bool, Option<&'static str>) {
56    let now = time::OffsetDateTime::now_utc().to_offset(UtcOffset::from_hms(8, 0, 0).unwrap());
57    let date = now.date();
58    let format = format_description::parse("[year]-[month]-[day]").unwrap();
59    is_offday(&date.format(&format).unwrap()).unwrap()
60}
61
62/// Check if current time (in UTC+8) is a workday
63///
64/// # Returns
65///
66/// * `bool` - true if it's a workday, false otherwise
67pub fn is_now_workday() -> bool {
68    let (is_off, _) = is_now_offday();
69    !is_off
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_is_offday() {
78        // Test a known holiday
79        let result = is_offday("2025-01-01").unwrap();
80        assert!(result.0); // Should be an off-day
81        assert_eq!(result.1.unwrap(), "元旦"); // Should be New Year's Day
82
83        // Test a regular workday
84        let result = is_offday("2025-01-02").unwrap(); // Thursday
85        assert!(!result.0); // Should not be an off-day
86        assert_eq!(result.1, None);
87
88        // Test a weekend
89        let result = is_offday("2025-01-04").unwrap(); // Saturday
90        assert!(result.0); // Should be an off-day (weekend)
91        assert_eq!(result.1, None);
92
93        // Test a weekend marked as workday
94        let result = is_offday("2025-09-28").unwrap(); // Sunday marked as workday
95        assert!(!result.0); // Should not be an off-day
96        assert_eq!(result.1.unwrap(), "国庆节、中秋节");
97    }
98
99    #[test]
100    fn test_is_workday() {
101        // Test a holiday
102        assert!(!is_workday("2025-01-01").unwrap()); // New Year's Day
103
104        // Test a normal weekday
105        assert!(is_workday("2025-01-02").unwrap()); // Thursday
106
107        // Test a weekend
108        assert!(!is_workday("2025-01-04").unwrap()); // Saturday
109
110        // Test a weekend marked as workday
111        assert!(is_workday("2025-09-28").unwrap()); // Sunday marked as workday
112    }
113
114    #[test]
115    fn test_invalid_date() {
116        assert!(is_offday("invalid-date").is_err());
117        assert!(is_workday("invalid-date").is_err());
118    }
119}