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 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 chrono::{Local, 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().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 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
242#[cfg(all(not(feature = "std"), feature = "core-error"))]
243impl core::error::Error for ParseWeekdayError {}
244
245#[cfg(feature = "std")]
246impl std::error::Error for ParseWeekdayError {}
247
248impl fmt::Display for ParseWeekdayError {
249    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250        f.write_fmt(format_args!("{self:?}"))
251    }
252}
253
254impl fmt::Debug for ParseWeekdayError {
255    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
256        write!(f, "ParseWeekdayError {{ .. }}")
257    }
258}
259
260#[cfg(feature = "defmt")]
261impl defmt::Format for ParseWeekdayError {
262    fn format(&self, fmt: defmt::Formatter) {
263        defmt::write!(fmt, "ParseWeekdayError {{ .. }}")
264    }
265}
266
267// the actual `FromStr` implementation is in the `format` module to leverage the existing code
268
269#[cfg(feature = "serde")]
270mod weekday_serde {
271    use super::Weekday;
272    use core::fmt;
273    use serde::{de, ser};
274
275    impl ser::Serialize for Weekday {
276        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277        where
278            S: ser::Serializer,
279        {
280            serializer.collect_str(&self)
281        }
282    }
283
284    struct WeekdayVisitor;
285
286    impl de::Visitor<'_> for WeekdayVisitor {
287        type Value = Weekday;
288
289        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
290            f.write_str("Weekday")
291        }
292
293        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
294        where
295            E: de::Error,
296        {
297            value.parse().map_err(|_| E::custom("short or long weekday names expected"))
298        }
299    }
300
301    impl<'de> de::Deserialize<'de> for Weekday {
302        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
303        where
304            D: de::Deserializer<'de>,
305        {
306            deserializer.deserialize_str(WeekdayVisitor)
307        }
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::Weekday;
314
315    #[test]
316    fn test_days_since() {
317        for i in 0..7 {
318            let base_day = Weekday::try_from(i).unwrap();
319
320            assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon));
321            assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun));
322
323            assert_eq!(base_day.days_since(base_day), 0);
324
325            assert_eq!(base_day.days_since(base_day.pred()), 1);
326            assert_eq!(base_day.days_since(base_day.pred().pred()), 2);
327            assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3);
328            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4);
329            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5);
330            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6);
331
332            assert_eq!(base_day.days_since(base_day.succ()), 6);
333            assert_eq!(base_day.days_since(base_day.succ().succ()), 5);
334            assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4);
335            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3);
336            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2);
337            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1);
338        }
339    }
340
341    #[test]
342    fn test_formatting_alignment() {
343        // No exhaustive testing here as we just delegate the
344        // implementation to Formatter::pad. Just some basic smoke
345        // testing to ensure that it's in fact being done.
346        assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon");
347        assert_eq!(format!("{:^7}", Weekday::Mon), "  Mon  ");
348        assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ");
349    }
350
351    #[test]
352    #[cfg(feature = "serde")]
353    fn test_serde_serialize() {
354        use Weekday::*;
355        use serde_json::to_string;
356
357        let cases: Vec<(Weekday, &str)> = vec![
358            (Mon, "\"Mon\""),
359            (Tue, "\"Tue\""),
360            (Wed, "\"Wed\""),
361            (Thu, "\"Thu\""),
362            (Fri, "\"Fri\""),
363            (Sat, "\"Sat\""),
364            (Sun, "\"Sun\""),
365        ];
366
367        for (weekday, expected_str) in cases {
368            let string = to_string(&weekday).unwrap();
369            assert_eq!(string, expected_str);
370        }
371    }
372
373    #[test]
374    #[cfg(feature = "serde")]
375    fn test_serde_deserialize() {
376        use Weekday::*;
377        use serde_json::from_str;
378
379        let cases: Vec<(&str, Weekday)> = vec![
380            ("\"mon\"", Mon),
381            ("\"MONDAY\"", Mon),
382            ("\"MonDay\"", Mon),
383            ("\"mOn\"", Mon),
384            ("\"tue\"", Tue),
385            ("\"tuesday\"", Tue),
386            ("\"wed\"", Wed),
387            ("\"wednesday\"", Wed),
388            ("\"thu\"", Thu),
389            ("\"thursday\"", Thu),
390            ("\"fri\"", Fri),
391            ("\"friday\"", Fri),
392            ("\"sat\"", Sat),
393            ("\"saturday\"", Sat),
394            ("\"sun\"", Sun),
395            ("\"sunday\"", Sun),
396        ];
397
398        for (str, expected_weekday) in cases {
399            let weekday = from_str::<Weekday>(str).unwrap();
400            assert_eq!(weekday, expected_weekday);
401        }
402
403        let errors: Vec<&str> =
404            vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""];
405
406        for str in errors {
407            from_str::<Weekday>(str).unwrap_err();
408        }
409    }
410
411    #[test]
412    #[cfg(feature = "rkyv-validation")]
413    fn test_rkyv_validation() {
414        let mon = Weekday::Mon;
415        let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap();
416
417        assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon);
418    }
419}