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 { 7 + lhs - rhs } else { lhs - rhs }
165    }
166}
167
168impl fmt::Display for Weekday {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        f.pad(match *self {
171            Weekday::Mon => "Mon",
172            Weekday::Tue => "Tue",
173            Weekday::Wed => "Wed",
174            Weekday::Thu => "Thu",
175            Weekday::Fri => "Fri",
176            Weekday::Sat => "Sat",
177            Weekday::Sun => "Sun",
178        })
179    }
180}
181
182/// Any weekday can be represented as an integer from 0 to 6, which equals to
183/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
184/// Do not heavily depend on this though; use explicit methods whenever possible.
185impl TryFrom<u8> for Weekday {
186    type Error = OutOfRange;
187
188    fn try_from(value: u8) -> Result<Self, Self::Error> {
189        match value {
190            0 => Ok(Weekday::Mon),
191            1 => Ok(Weekday::Tue),
192            2 => Ok(Weekday::Wed),
193            3 => Ok(Weekday::Thu),
194            4 => Ok(Weekday::Fri),
195            5 => Ok(Weekday::Sat),
196            6 => Ok(Weekday::Sun),
197            _ => Err(OutOfRange::new()),
198        }
199    }
200}
201
202/// Any weekday can be represented as an integer from 0 to 6, which equals to
203/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation.
204/// Do not heavily depend on this though; use explicit methods whenever possible.
205impl num_traits::FromPrimitive for Weekday {
206    #[inline]
207    fn from_i64(n: i64) -> Option<Weekday> {
208        match n {
209            0 => Some(Weekday::Mon),
210            1 => Some(Weekday::Tue),
211            2 => Some(Weekday::Wed),
212            3 => Some(Weekday::Thu),
213            4 => Some(Weekday::Fri),
214            5 => Some(Weekday::Sat),
215            6 => Some(Weekday::Sun),
216            _ => None,
217        }
218    }
219
220    #[inline]
221    fn from_u64(n: u64) -> Option<Weekday> {
222        match n {
223            0 => Some(Weekday::Mon),
224            1 => Some(Weekday::Tue),
225            2 => Some(Weekday::Wed),
226            3 => Some(Weekday::Thu),
227            4 => Some(Weekday::Fri),
228            5 => Some(Weekday::Sat),
229            6 => Some(Weekday::Sun),
230            _ => None,
231        }
232    }
233}
234
235/// An error resulting from reading `Weekday` value with `FromStr`.
236#[derive(Clone, PartialEq, Eq)]
237pub struct ParseWeekdayError {
238    pub(crate) _dummy: (),
239}
240
241#[cfg(all(not(feature = "std"), feature = "core-error"))]
242impl core::error::Error for ParseWeekdayError {}
243
244#[cfg(feature = "std")]
245impl std::error::Error for ParseWeekdayError {}
246
247impl fmt::Display for ParseWeekdayError {
248    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249        f.write_fmt(format_args!("{self:?}"))
250    }
251}
252
253impl fmt::Debug for ParseWeekdayError {
254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255        write!(f, "ParseWeekdayError {{ .. }}")
256    }
257}
258
259// the actual `FromStr` implementation is in the `format` module to leverage the existing code
260
261#[cfg(feature = "serde")]
262mod weekday_serde {
263    use super::Weekday;
264    use core::fmt;
265    use serde::{de, ser};
266
267    impl ser::Serialize for Weekday {
268        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269        where
270            S: ser::Serializer,
271        {
272            serializer.collect_str(&self)
273        }
274    }
275
276    struct WeekdayVisitor;
277
278    impl de::Visitor<'_> for WeekdayVisitor {
279        type Value = Weekday;
280
281        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
282            f.write_str("Weekday")
283        }
284
285        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
286        where
287            E: de::Error,
288        {
289            value.parse().map_err(|_| E::custom("short or long weekday names expected"))
290        }
291    }
292
293    impl<'de> de::Deserialize<'de> for Weekday {
294        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
295        where
296            D: de::Deserializer<'de>,
297        {
298            deserializer.deserialize_str(WeekdayVisitor)
299        }
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::Weekday;
306
307    #[test]
308    fn test_days_since() {
309        for i in 0..7 {
310            let base_day = Weekday::try_from(i).unwrap();
311
312            assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon));
313            assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun));
314
315            assert_eq!(base_day.days_since(base_day), 0);
316
317            assert_eq!(base_day.days_since(base_day.pred()), 1);
318            assert_eq!(base_day.days_since(base_day.pred().pred()), 2);
319            assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3);
320            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4);
321            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5);
322            assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6);
323
324            assert_eq!(base_day.days_since(base_day.succ()), 6);
325            assert_eq!(base_day.days_since(base_day.succ().succ()), 5);
326            assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4);
327            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3);
328            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2);
329            assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1);
330        }
331    }
332
333    #[test]
334    fn test_formatting_alignment() {
335        // No exhaustive testing here as we just delegate the
336        // implementation to Formatter::pad. Just some basic smoke
337        // testing to ensure that it's in fact being done.
338        assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon");
339        assert_eq!(format!("{:^7}", Weekday::Mon), "  Mon  ");
340        assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ");
341    }
342
343    #[test]
344    #[cfg(feature = "serde")]
345    fn test_serde_serialize() {
346        use Weekday::*;
347        use serde_json::to_string;
348
349        let cases: Vec<(Weekday, &str)> = vec![
350            (Mon, "\"Mon\""),
351            (Tue, "\"Tue\""),
352            (Wed, "\"Wed\""),
353            (Thu, "\"Thu\""),
354            (Fri, "\"Fri\""),
355            (Sat, "\"Sat\""),
356            (Sun, "\"Sun\""),
357        ];
358
359        for (weekday, expected_str) in cases {
360            let string = to_string(&weekday).unwrap();
361            assert_eq!(string, expected_str);
362        }
363    }
364
365    #[test]
366    #[cfg(feature = "serde")]
367    fn test_serde_deserialize() {
368        use Weekday::*;
369        use serde_json::from_str;
370
371        let cases: Vec<(&str, Weekday)> = vec![
372            ("\"mon\"", Mon),
373            ("\"MONDAY\"", Mon),
374            ("\"MonDay\"", Mon),
375            ("\"mOn\"", Mon),
376            ("\"tue\"", Tue),
377            ("\"tuesday\"", Tue),
378            ("\"wed\"", Wed),
379            ("\"wednesday\"", Wed),
380            ("\"thu\"", Thu),
381            ("\"thursday\"", Thu),
382            ("\"fri\"", Fri),
383            ("\"friday\"", Fri),
384            ("\"sat\"", Sat),
385            ("\"saturday\"", Sat),
386            ("\"sun\"", Sun),
387            ("\"sunday\"", Sun),
388        ];
389
390        for (str, expected_weekday) in cases {
391            let weekday = from_str::<Weekday>(str).unwrap();
392            assert_eq!(weekday, expected_weekday);
393        }
394
395        let errors: Vec<&str> =
396            vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""];
397
398        for str in errors {
399            from_str::<Weekday>(str).unwrap_err();
400        }
401    }
402
403    #[test]
404    #[cfg(feature = "rkyv-validation")]
405    fn test_rkyv_validation() {
406        let mon = Weekday::Mon;
407        let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap();
408
409        assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon);
410    }
411}