Skip to main content

ai_chrono/
weekday.rs

1use core::fmt;
2
3#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
4use rkyv::{Archive, Deserialize, Serialize};
5
6use crate::OutOfRange;
7
8/// The day of week.
9///
10/// The order of the days of week depends on the context.
11/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.)
12/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result.
13///
14/// # Example
15/// ```
16/// use ai_chrono::Weekday;
17///
18/// let monday = "Monday".parse::<Weekday>().unwrap();
19/// assert_eq!(monday, Weekday::Mon);
20///
21/// let sunday = Weekday::try_from(6).unwrap();
22/// assert_eq!(sunday, Weekday::Sun);
23///
24/// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0
25/// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1
26/// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0
27/// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1
28///
29/// assert_eq!(sunday.succ(), monday);
30/// assert_eq!(sunday.pred(), Weekday::Sat);
31/// ```
32#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
33#[cfg_attr(
34    any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
35    derive(Archive, Deserialize, Serialize),
36    archive(compare(PartialEq)),
37    archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash))
38)]
39#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
40#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42pub enum Weekday {
43    /// Monday.
44    Mon = 0,
45    /// Tuesday.
46    Tue = 1,
47    /// Wednesday.
48    Wed = 2,
49    /// Thursday.
50    Thu = 3,
51    /// Friday.
52    Fri = 4,
53    /// Saturday.
54    Sat = 5,
55    /// Sunday.
56    Sun = 6,
57}
58
59impl Weekday {
60    /// The next day in the week.
61    ///
62    /// `w`:        | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
63    /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
64    /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon`
65    #[inline]
66    #[must_use]
67    pub const fn succ(&self) -> Weekday {
68        match *self {
69            Weekday::Mon => Weekday::Tue,
70            Weekday::Tue => Weekday::Wed,
71            Weekday::Wed => Weekday::Thu,
72            Weekday::Thu => Weekday::Fri,
73            Weekday::Fri => Weekday::Sat,
74            Weekday::Sat => Weekday::Sun,
75            Weekday::Sun => Weekday::Mon,
76        }
77    }
78
79    /// The previous day in the week.
80    ///
81    /// `w`:        | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
82    /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
83    /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat`
84    #[inline]
85    #[must_use]
86    pub const fn pred(&self) -> Weekday {
87        match *self {
88            Weekday::Mon => Weekday::Sun,
89            Weekday::Tue => Weekday::Mon,
90            Weekday::Wed => Weekday::Tue,
91            Weekday::Thu => Weekday::Wed,
92            Weekday::Fri => Weekday::Thu,
93            Weekday::Sat => Weekday::Fri,
94            Weekday::Sun => Weekday::Sat,
95        }
96    }
97
98    /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number)
99    ///
100    /// `w`:                      | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
101    /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
102    /// `w.number_from_monday()`: | 1     | 2     | 3     | 4     | 5     | 6     | 7
103    #[inline]
104    pub const fn number_from_monday(&self) -> u32 {
105        self.days_since(Weekday::Mon) + 1
106    }
107
108    /// Returns a day-of-week number starting from Sunday = 1.
109    ///
110    /// `w`:                      | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
111    /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
112    /// `w.number_from_sunday()`: | 2     | 3     | 4     | 5     | 6     | 7     | 1
113    #[inline]
114    pub const fn number_from_sunday(&self) -> u32 {
115        self.days_since(Weekday::Sun) + 1
116    }
117
118    /// Returns a day-of-week number starting from Monday = 0.
119    ///
120    /// `w`:                        | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
121    /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
122    /// `w.num_days_from_monday()`: | 0     | 1     | 2     | 3     | 4     | 5     | 6
123    ///
124    /// # Example
125    ///
126    /// ```
127    /// # #[cfg(feature = "clock")] {
128    /// # use ai_chrono::{Local, StdNow, Datelike};
129    /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays.
130    /// // Use `num_days_from_monday` to index into the array.
131    /// const MTWRFSU: [char; 7] = ['M', 'T', 'W', 'R', 'F', 'S', 'U'];
132    ///
133    /// let today = Local::now::<StdNow>().weekday();
134    /// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]);
135    /// # }
136    /// ```
137    #[inline]
138    pub const fn num_days_from_monday(&self) -> u32 {
139        self.days_since(Weekday::Mon)
140    }
141
142    /// Returns a day-of-week number starting from Sunday = 0.
143    ///
144    /// `w`:                        | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
145    /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
146    /// `w.num_days_from_sunday()`: | 1     | 2     | 3     | 4     | 5     | 6     | 0
147    #[inline]
148    pub const fn num_days_from_sunday(&self) -> u32 {
149        self.days_since(Weekday::Sun)
150    }
151
152    /// The number of days since the given day.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use ai_chrono::Weekday::*;
158    /// assert_eq!(Mon.days_since(Mon), 0);
159    /// assert_eq!(Sun.days_since(Tue), 5);
160    /// assert_eq!(Wed.days_since(Sun), 3);
161    /// ```
162    pub const fn days_since(&self, other: Weekday) -> u32 {
163        let lhs = *self as u32;
164        let rhs = other as u32;
165        if lhs < rhs { 7 + lhs - rhs } else { lhs - rhs }
166    }
167}
168
169impl fmt::Display for Weekday {
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        f.pad(match *self {
172            Weekday::Mon => "Mon",
173            Weekday::Tue => "Tue",
174            Weekday::Wed => "Wed",
175            Weekday::Thu => "Thu",
176            Weekday::Fri => "Fri",
177            Weekday::Sat => "Sat",
178            Weekday::Sun => "Sun",
179        })
180    }
181}
182
183/// Any weekday can be represented as an integer from 0 to 6, which equals to
184/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
185/// Do not heavily depend on this though; use explicit methods whenever possible.
186impl TryFrom<u8> for Weekday {
187    type Error = OutOfRange;
188
189    fn try_from(value: u8) -> Result<Self, Self::Error> {
190        match value {
191            0 => Ok(Weekday::Mon),
192            1 => Ok(Weekday::Tue),
193            2 => Ok(Weekday::Wed),
194            3 => Ok(Weekday::Thu),
195            4 => Ok(Weekday::Fri),
196            5 => Ok(Weekday::Sat),
197            6 => Ok(Weekday::Sun),
198            _ => Err(OutOfRange::new()),
199        }
200    }
201}
202
203/// Any weekday can be represented as an integer from 0 to 6, which equals to
204/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
205/// Do not heavily depend on this though; use explicit methods whenever possible.
206impl num_traits::FromPrimitive for Weekday {
207    #[inline]
208    fn from_i64(n: i64) -> Option<Weekday> {
209        match n {
210            0 => Some(Weekday::Mon),
211            1 => Some(Weekday::Tue),
212            2 => Some(Weekday::Wed),
213            3 => Some(Weekday::Thu),
214            4 => Some(Weekday::Fri),
215            5 => Some(Weekday::Sat),
216            6 => Some(Weekday::Sun),
217            _ => None,
218        }
219    }
220
221    #[inline]
222    fn from_u64(n: u64) -> Option<Weekday> {
223        match n {
224            0 => Some(Weekday::Mon),
225            1 => Some(Weekday::Tue),
226            2 => Some(Weekday::Wed),
227            3 => Some(Weekday::Thu),
228            4 => Some(Weekday::Fri),
229            5 => Some(Weekday::Sat),
230            6 => Some(Weekday::Sun),
231            _ => None,
232        }
233    }
234}
235
236/// An error resulting from reading `Weekday` value with `FromStr`.
237#[derive(Clone, PartialEq, Eq)]
238pub struct ParseWeekdayError {
239    pub(crate) _dummy: (),
240}
241
242impl core::error::Error for ParseWeekdayError {}
243
244impl fmt::Display for ParseWeekdayError {
245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246        f.write_fmt(format_args!("{self:?}"))
247    }
248}
249
250impl fmt::Debug for ParseWeekdayError {
251    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252        write!(f, "ParseWeekdayError {{ .. }}")
253    }
254}
255
256#[cfg(feature = "defmt")]
257impl defmt::Format for ParseWeekdayError {
258    fn format(&self, fmt: defmt::Formatter) {
259        defmt::write!(fmt, "ParseWeekdayError {{ .. }}")
260    }
261}
262
263// the actual `FromStr` implementation is in the `format` module to leverage the existing code
264
265#[cfg(feature = "serde")]
266mod weekday_serde {
267    use super::Weekday;
268    use core::fmt;
269    use serde::{de, ser};
270
271    impl ser::Serialize for Weekday {
272        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
273        where
274            S: ser::Serializer,
275        {
276            serializer.collect_str(&self)
277        }
278    }
279
280    struct WeekdayVisitor;
281
282    impl de::Visitor<'_> for WeekdayVisitor {
283        type Value = Weekday;
284
285        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
286            f.write_str("Weekday")
287        }
288
289        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
290        where
291            E: de::Error,
292        {
293            value.parse().map_err(|_| E::custom("short or long weekday names expected"))
294        }
295    }
296
297    impl<'de> de::Deserialize<'de> for Weekday {
298        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
299        where
300            D: de::Deserializer<'de>,
301        {
302            deserializer.deserialize_str(WeekdayVisitor)
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::Weekday;
310
311    #[test]
312    fn test_days_since() {
313        for i in 0..7 {
314            let base_day = Weekday::try_from(i).unwrap();
315
316            assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon));
317            assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun));
318
319            assert_eq!(base_day.days_since(base_day), 0);
320
321            assert_eq!(base_day.days_since(base_day.pred()), 1);
322            assert_eq!(base_day.days_since(base_day.pred().pred()), 2);
323            assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3);
324            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4);
325            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5);
326            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6);
327
328            assert_eq!(base_day.days_since(base_day.succ()), 6);
329            assert_eq!(base_day.days_since(base_day.succ().succ()), 5);
330            assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4);
331            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3);
332            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2);
333            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1);
334        }
335    }
336
337    #[test]
338    fn test_formatting_alignment() {
339        // No exhaustive testing here as we just delegate the
340        // implementation to Formatter::pad. Just some basic smoke
341        // testing to ensure that it's in fact being done.
342        assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon");
343        assert_eq!(format!("{:^7}", Weekday::Mon), "  Mon  ");
344        assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ");
345    }
346
347    #[test]
348    #[cfg(feature = "serde")]
349    fn test_serde_serialize() {
350        use Weekday::*;
351        use serde_json::to_string;
352
353        let cases: Vec<(Weekday, &str)> = vec![
354            (Mon, "\"Mon\""),
355            (Tue, "\"Tue\""),
356            (Wed, "\"Wed\""),
357            (Thu, "\"Thu\""),
358            (Fri, "\"Fri\""),
359            (Sat, "\"Sat\""),
360            (Sun, "\"Sun\""),
361        ];
362
363        for (weekday, expected_str) in cases {
364            let string = to_string(&weekday).unwrap();
365            assert_eq!(string, expected_str);
366        }
367    }
368
369    #[test]
370    #[cfg(feature = "serde")]
371    fn test_serde_deserialize() {
372        use Weekday::*;
373        use serde_json::from_str;
374
375        let cases: Vec<(&str, Weekday)> = vec![
376            ("\"mon\"", Mon),
377            ("\"MONDAY\"", Mon),
378            ("\"MonDay\"", Mon),
379            ("\"mOn\"", Mon),
380            ("\"tue\"", Tue),
381            ("\"tuesday\"", Tue),
382            ("\"wed\"", Wed),
383            ("\"wednesday\"", Wed),
384            ("\"thu\"", Thu),
385            ("\"thursday\"", Thu),
386            ("\"fri\"", Fri),
387            ("\"friday\"", Fri),
388            ("\"sat\"", Sat),
389            ("\"saturday\"", Sat),
390            ("\"sun\"", Sun),
391            ("\"sunday\"", Sun),
392        ];
393
394        for (str, expected_weekday) in cases {
395            let weekday = from_str::<Weekday>(str).unwrap();
396            assert_eq!(weekday, expected_weekday);
397        }
398
399        let errors: Vec<&str> =
400            vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""];
401
402        for str in errors {
403            from_str::<Weekday>(str).unwrap_err();
404        }
405    }
406
407    #[test]
408    #[cfg(feature = "rkyv-validation")]
409    fn test_rkyv_validation() {
410        let mon = Weekday::Mon;
411        let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap();
412
413        assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon);
414    }
415}