Skip to main content

use_weekday/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive weekday helpers.
3//!
4//! The helpers here compute weekdays deterministically from Gregorian dates.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_weekday::{Weekday, is_weekend, weekday_for_date};
10//!
11//! let weekday = weekday_for_date(2024, 5, 17).unwrap();
12//!
13//! assert_eq!(weekday, Weekday::Friday);
14//! assert_eq!(weekday.number_from_monday(), 5);
15//! assert!(!is_weekend(weekday));
16//! ```
17
18use use_date::{days_between, CalendarDate};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Weekday {
22    Monday,
23    Tuesday,
24    Wednesday,
25    Thursday,
26    Friday,
27    Saturday,
28    Sunday,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum WeekdayError {
33    InvalidDate,
34}
35
36impl Weekday {
37    #[must_use]
38    pub fn number_from_monday(&self) -> u8 {
39        match self {
40            Self::Monday => 1,
41            Self::Tuesday => 2,
42            Self::Wednesday => 3,
43            Self::Thursday => 4,
44            Self::Friday => 5,
45            Self::Saturday => 6,
46            Self::Sunday => 7,
47        }
48    }
49
50    #[must_use]
51    pub fn number_from_sunday(&self) -> u8 {
52        match self {
53            Self::Sunday => 1,
54            Self::Monday => 2,
55            Self::Tuesday => 3,
56            Self::Wednesday => 4,
57            Self::Thursday => 5,
58            Self::Friday => 6,
59            Self::Saturday => 7,
60        }
61    }
62
63    #[must_use]
64    pub fn is_weekend(&self) -> bool {
65        matches!(self, Self::Saturday | Self::Sunday)
66    }
67
68    #[must_use]
69    pub fn is_weekday(&self) -> bool {
70        !self.is_weekend()
71    }
72}
73
74pub fn weekday_for_date(year: i32, month: u8, day: u8) -> Result<Weekday, WeekdayError> {
75    let epoch = CalendarDate::new(1970, 1, 1).unwrap();
76    let date = CalendarDate::new(year, month, day).map_err(|_| WeekdayError::InvalidDate)?;
77    let offset = days_between(epoch, date).rem_euclid(7);
78
79    Ok(match offset {
80        0 => Weekday::Thursday,
81        1 => Weekday::Friday,
82        2 => Weekday::Saturday,
83        3 => Weekday::Sunday,
84        4 => Weekday::Monday,
85        5 => Weekday::Tuesday,
86        _ => Weekday::Wednesday,
87    })
88}
89
90#[must_use]
91pub fn is_weekend(weekday: Weekday) -> bool {
92    weekday.is_weekend()
93}
94
95#[must_use]
96pub fn is_weekday(weekday: Weekday) -> bool {
97    weekday.is_weekday()
98}
99
100#[cfg(test)]
101mod tests {
102    use super::{is_weekday, is_weekend, weekday_for_date, Weekday, WeekdayError};
103
104    #[test]
105    fn calculates_known_weekdays() {
106        assert_eq!(weekday_for_date(1970, 1, 1).unwrap(), Weekday::Thursday);
107        assert_eq!(weekday_for_date(2024, 2, 29).unwrap(), Weekday::Thursday);
108        assert_eq!(weekday_for_date(2024, 5, 17).unwrap(), Weekday::Friday);
109    }
110
111    #[test]
112    fn checks_weekend_helpers() {
113        assert_eq!(Weekday::Monday.number_from_monday(), 1);
114        assert_eq!(Weekday::Sunday.number_from_sunday(), 1);
115        assert!(Weekday::Saturday.is_weekend());
116        assert!(Weekday::Tuesday.is_weekday());
117        assert!(is_weekday(Weekday::Friday));
118        assert!(is_weekend(Weekday::Sunday));
119    }
120
121    #[test]
122    fn rejects_invalid_weekday_dates() {
123        assert_eq!(
124            weekday_for_date(2024, 2, 30),
125            Err(WeekdayError::InvalidDate)
126        );
127    }
128}