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