1#![forbid(unsafe_code)]
2use 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}