Skip to main content

embedded_date_time/
weekday.rs

1use core::fmt;
2
3/// Day of the week starting from Monday = 1 (ISO 8601).
4///
5/// The ordinal value of 0 is not being used by common RTC chips.
6/// This place can be used to re-map Sunday to 0 in order to obtain a week starting from Sunday.
7///
8/// `PartialOrd` isn't implemented on purpose because weekdays are usually used in a cyclic manner.
9#[derive(PartialEq, Eq, Hash, Copy, Clone)]
10#[repr(u8)]
11pub enum Weekday {
12    /// Monday.
13    Mon = Self::MON_NUM,
14    /// Tuesday.
15    Tue = Self::TUE_NUM,
16    /// Wednesday.
17    Wed = Self::WED_NUM,
18    /// Thursday.
19    Thu = Self::THU_NUM,
20    /// Friday.
21    Fri = Self::FRI_NUM,
22    /// Saturday.
23    Sat = Self::SAT_NUM,
24    /// Sunday.
25    Sun = Self::SUN_NUM,
26}
27
28impl Weekday {
29    /// the numeric code being used for Monday.
30    pub const MON_NUM: u8 = 1;
31    /// the numeric code being used for Tuesday.
32    pub const TUE_NUM: u8 = 2;
33    /// the numeric code being used for Wednesday.
34    pub const WED_NUM: u8 = 3;
35    /// the numeric code being used for Thursday.
36    pub const THU_NUM: u8 = 4;
37    /// the numeric code being used for Friday.
38    pub const FRI_NUM: u8 = 5;
39    /// the numeric code being used for Saturday.
40    pub const SAT_NUM: u8 = 6;
41    /// the numeric code being used for Sunday.
42    pub const SUN_NUM: u8 = 7;
43
44    /// the short name of Monday in English.
45    pub const MON: &str = "Mon";
46    /// the short name of Tuesday in English.
47    pub const TUE: &str = "Tue";
48    /// the short name of Wednesday in English.
49    pub const WED: &str = "Wed";
50    /// the short name of Thursday in English.
51    pub const THU: &str = "Thu";
52    /// the short name of Friday in English.
53    pub const FRI: &str = "Fri";
54    /// the short name of Saturday in English.
55    pub const SAT: &str = "Sat";
56    /// the short name of Sunday in English.
57    pub const SUN: &str = "Sun";
58
59    /// Returns a day-of-week number starting from Monday = 0.
60    #[must_use]
61    pub fn num_days_from_monday(self) -> u8 {
62        self as u8 - 1
63    }
64
65    /// Returns a day-of-week number starting from Monday = 1.
66    #[must_use]
67    pub fn number_from_monday(self) -> u8 {
68        self as u8
69    }
70
71    /// Returns a day-of-week number starting from Sunday = 0.
72    #[must_use]
73    pub fn num_days_from_sunday(self) -> u8 {
74        // avoid modulo operation
75        if self == Self::Sun { 0 } else { self as u8 }
76    }
77
78    /// Returns a day-of-week number starting from Sunday = 1.
79    #[must_use]
80    pub fn number_from_sunday(self) -> u8 {
81        // avoid modulo operation
82        if self == Self::Sun { 1 } else { self as u8 + 1 }
83    }
84
85    /// Return the weekday's short name in English.
86    ///
87    /// The returned string is guaranteed to be 3 characters long.
88    #[must_use]
89    pub fn as_str(self) -> &'static str {
90        match self {
91            Self::Mon => Self::MON,
92            Self::Tue => Self::TUE,
93            Self::Wed => Self::WED,
94            Self::Thu => Self::THU,
95            Self::Fri => Self::FRI,
96            Self::Sat => Self::SAT,
97            Self::Sun => Self::SUN,
98        }
99    }
100}
101
102impl From<u8> for Weekday {
103    fn from(value: u8) -> Self {
104        // cheaply maps the value to the corresponding weekday
105        // Sunday can be obtained from 0 or 7
106        match value {
107            Self::MON_NUM => Self::Mon,
108            Self::TUE_NUM => Self::Tue,
109            Self::WED_NUM => Self::Wed,
110            Self::THU_NUM => Self::Thu,
111            Self::FRI_NUM => Self::Fri,
112            Self::SAT_NUM => Self::Sat,
113            _ => Self::Sun,
114        }
115    }
116}
117
118impl AsRef<str> for Weekday {
119    fn as_ref(&self) -> &str {
120        self.as_str()
121    }
122}
123
124impl fmt::Debug for Weekday {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        fmt::Display::fmt(&self, f)
127    }
128}
129
130impl fmt::Display for Weekday {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.write_str(self.as_str())
133    }
134}
135
136#[cfg(feature = "defmt")]
137impl defmt::Format for Weekday {
138    fn format(&self, fmt: defmt::Formatter<'_>) {
139        defmt::write!(fmt, "{}", self.as_str());
140    }
141}
142
143#[cfg(feature = "ufmt")]
144impl ufmt::uDebug for Weekday {
145    fn fmt<W>(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
146    where
147        W: ufmt::uWrite + ?Sized,
148    {
149        ufmt::uDisplay::fmt(&self, fmt)
150    }
151}
152
153#[cfg(feature = "ufmt")]
154impl ufmt::uDisplay for Weekday {
155    fn fmt<W>(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
156    where
157        W: ufmt::uWrite + ?Sized,
158    {
159        fmt.write_str(self.as_str())
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    #![expect(clippy::panic, clippy::unwrap_used, reason = "this is a test")]
166
167    use chrono::Datelike as _;
168
169    #[test]
170    fn test_weekday() {
171        // iterate through all valid dates and test whether `chrono` and `embedded-date-time` agree
172        // on the weekday calculation.
173        for year in 2000_u16..=2171 {
174            'next_month: for month in 1_u8..=12 {
175                for day in 1_u8..=31 {
176                    let Some(embedded_date) = crate::Date::new_checked(year, month, day) else {
177                        continue 'next_month;
178                    };
179                    let chrono_date = chrono::NaiveDate::from_ymd_opt(
180                        i32::from(year),
181                        u32::from(month),
182                        u32::from(day),
183                    )
184                    .unwrap();
185
186                    let chrono_weekday = chrono_date.weekday();
187                    let embedded_weekday = embedded_date.weekday();
188
189                    match (chrono_weekday, embedded_weekday) {
190                        (chrono::Weekday::Mon, super::Weekday::Mon)
191                        | (chrono::Weekday::Tue, super::Weekday::Tue)
192                        | (chrono::Weekday::Wed, super::Weekday::Wed)
193                        | (chrono::Weekday::Thu, super::Weekday::Thu)
194                        | (chrono::Weekday::Fri, super::Weekday::Fri)
195                        | (chrono::Weekday::Sat, super::Weekday::Sat)
196                        | (chrono::Weekday::Sun, super::Weekday::Sun) => (),
197                        _ => panic!(
198                            "chrono and embedded weekdays do not match: {chrono_weekday:?} != {embedded_weekday:?} for {chrono_date}"
199                        ),
200                    }
201
202                    assert_eq!(
203                        chrono_weekday.num_days_from_monday(),
204                        u32::from(embedded_weekday.num_days_from_monday()),
205                        "num_days_from_monday: {chrono_date}",
206                    );
207
208                    assert_eq!(
209                        chrono_weekday.num_days_from_sunday(),
210                        u32::from(embedded_weekday.num_days_from_sunday()),
211                        "num_days_from_sunday: {chrono_date}",
212                    );
213
214                    assert_eq!(
215                        chrono_weekday.number_from_monday(),
216                        u32::from(embedded_weekday.number_from_monday()),
217                        "number_from_monday: {chrono_date}",
218                    );
219
220                    assert_eq!(
221                        chrono_weekday.number_from_sunday(),
222                        u32::from(embedded_weekday.number_from_sunday()),
223                        "number_from_sunday: {chrono_date}",
224                    );
225                }
226            }
227        }
228    }
229}