chrono/
month.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 month of the year.
9///
10/// This enum is just a convenience implementation.
11/// The month in dates created by DateLike objects does not return this enum.
12///
13/// It is possible to convert from a date to a month independently
14/// ```
15/// use chrono::prelude::*;
16/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
17/// // `2019-10-28T09:10:11Z`
18/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok();
19/// assert_eq!(month, Some(Month::October))
20/// ```
21/// Or from a Month to an integer usable by dates
22/// ```
23/// # use chrono::prelude::*;
24/// let month = Month::January;
25/// let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
26/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
27/// ```
28/// Allows mapping from and to month, from 1-January to 12-December.
29/// Can be Serialized/Deserialized with serde
30// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior.
31#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)]
32#[cfg_attr(
33    any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
34    derive(Archive, Deserialize, Serialize),
35    archive(compare(PartialEq, PartialOrd)),
36    archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash))
37)]
38#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
39#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
40pub enum Month {
41    /// January
42    January = 0,
43    /// February
44    February = 1,
45    /// March
46    March = 2,
47    /// April
48    April = 3,
49    /// May
50    May = 4,
51    /// June
52    June = 5,
53    /// July
54    July = 6,
55    /// August
56    August = 7,
57    /// September
58    September = 8,
59    /// October
60    October = 9,
61    /// November
62    November = 10,
63    /// December
64    December = 11,
65}
66
67impl Month {
68    /// The next month.
69    ///
70    /// `m`:        | `January`  | `February` | `...` | `December`
71    /// ----------- | ---------  | ---------- | --- | ---------
72    /// `m.succ()`: | `February` | `March`    | `...` | `January`
73    #[inline]
74    #[must_use]
75    pub const fn succ(&self) -> Month {
76        match *self {
77            Month::January => Month::February,
78            Month::February => Month::March,
79            Month::March => Month::April,
80            Month::April => Month::May,
81            Month::May => Month::June,
82            Month::June => Month::July,
83            Month::July => Month::August,
84            Month::August => Month::September,
85            Month::September => Month::October,
86            Month::October => Month::November,
87            Month::November => Month::December,
88            Month::December => Month::January,
89        }
90    }
91
92    /// The previous month.
93    ///
94    /// `m`:        | `January`  | `February` | `...` | `December`
95    /// ----------- | ---------  | ---------- | --- | ---------
96    /// `m.pred()`: | `December` | `January`  | `...` | `November`
97    #[inline]
98    #[must_use]
99    pub const fn pred(&self) -> Month {
100        match *self {
101            Month::January => Month::December,
102            Month::February => Month::January,
103            Month::March => Month::February,
104            Month::April => Month::March,
105            Month::May => Month::April,
106            Month::June => Month::May,
107            Month::July => Month::June,
108            Month::August => Month::July,
109            Month::September => Month::August,
110            Month::October => Month::September,
111            Month::November => Month::October,
112            Month::December => Month::November,
113        }
114    }
115
116    /// Returns a month-of-year number starting from January = 1.
117    ///
118    /// `m`:                     | `January` | `February` | `...` | `December`
119    /// -------------------------| --------- | ---------- | --- | -----
120    /// `m.number_from_month()`: | 1         | 2          | `...` | 12
121    #[inline]
122    #[must_use]
123    pub const fn number_from_month(&self) -> u32 {
124        match *self {
125            Month::January => 1,
126            Month::February => 2,
127            Month::March => 3,
128            Month::April => 4,
129            Month::May => 5,
130            Month::June => 6,
131            Month::July => 7,
132            Month::August => 8,
133            Month::September => 9,
134            Month::October => 10,
135            Month::November => 11,
136            Month::December => 12,
137        }
138    }
139
140    /// Get the name of the month
141    ///
142    /// ```
143    /// use chrono::Month;
144    ///
145    /// assert_eq!(Month::January.name(), "January")
146    /// ```
147    #[must_use]
148    pub const fn name(&self) -> &'static str {
149        match *self {
150            Month::January => "January",
151            Month::February => "February",
152            Month::March => "March",
153            Month::April => "April",
154            Month::May => "May",
155            Month::June => "June",
156            Month::July => "July",
157            Month::August => "August",
158            Month::September => "September",
159            Month::October => "October",
160            Month::November => "November",
161            Month::December => "December",
162        }
163    }
164}
165
166impl TryFrom<u8> for Month {
167    type Error = OutOfRange;
168
169    fn try_from(value: u8) -> Result<Self, Self::Error> {
170        match value {
171            1 => Ok(Month::January),
172            2 => Ok(Month::February),
173            3 => Ok(Month::March),
174            4 => Ok(Month::April),
175            5 => Ok(Month::May),
176            6 => Ok(Month::June),
177            7 => Ok(Month::July),
178            8 => Ok(Month::August),
179            9 => Ok(Month::September),
180            10 => Ok(Month::October),
181            11 => Ok(Month::November),
182            12 => Ok(Month::December),
183            _ => Err(OutOfRange::new()),
184        }
185    }
186}
187
188impl num_traits::FromPrimitive for Month {
189    /// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1.
190    ///
191    /// `Month::from_i64(n: i64)`: | `1`                  | `2`                   | ... | `12`
192    /// ---------------------------| -------------------- | --------------------- | ... | -----
193    /// ``:                        | Some(Month::January) | Some(Month::February) | ... | Some(Month::December)
194    #[inline]
195    fn from_u64(n: u64) -> Option<Month> {
196        Self::from_u32(n as u32)
197    }
198
199    #[inline]
200    fn from_i64(n: i64) -> Option<Month> {
201        Self::from_u32(n as u32)
202    }
203
204    #[inline]
205    fn from_u32(n: u32) -> Option<Month> {
206        match n {
207            1 => Some(Month::January),
208            2 => Some(Month::February),
209            3 => Some(Month::March),
210            4 => Some(Month::April),
211            5 => Some(Month::May),
212            6 => Some(Month::June),
213            7 => Some(Month::July),
214            8 => Some(Month::August),
215            9 => Some(Month::September),
216            10 => Some(Month::October),
217            11 => Some(Month::November),
218            12 => Some(Month::December),
219            _ => None,
220        }
221    }
222}
223
224/// A duration in calendar months
225#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
226#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
227pub struct Months(pub(crate) u32);
228
229impl Months {
230    /// Construct a new `Months` from a number of months
231    pub const fn new(num: u32) -> Self {
232        Self(num)
233    }
234
235    /// Returns the total number of months in the `Months` instance.
236    #[inline]
237    pub const fn as_u32(&self) -> u32 {
238        self.0
239    }
240}
241
242/// An error resulting from reading `<Month>` value with `FromStr`.
243#[derive(Clone, PartialEq, Eq)]
244pub struct ParseMonthError {
245    pub(crate) _dummy: (),
246}
247
248#[cfg(feature = "std")]
249impl std::error::Error for ParseMonthError {}
250
251impl fmt::Display for ParseMonthError {
252    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253        write!(f, "ParseMonthError {{ .. }}")
254    }
255}
256
257impl fmt::Debug for ParseMonthError {
258    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259        write!(f, "ParseMonthError {{ .. }}")
260    }
261}
262
263#[cfg(feature = "serde")]
264mod month_serde {
265    use super::Month;
266    use serde::{de, ser};
267
268    use core::fmt;
269
270    impl ser::Serialize for Month {
271        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272        where
273            S: ser::Serializer,
274        {
275            serializer.collect_str(self.name())
276        }
277    }
278
279    struct MonthVisitor;
280
281    impl de::Visitor<'_> for MonthVisitor {
282        type Value = Month;
283
284        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
285            f.write_str("Month")
286        }
287
288        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
289        where
290            E: de::Error,
291        {
292            value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected"))
293        }
294    }
295
296    impl<'de> de::Deserialize<'de> for Month {
297        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298        where
299            D: de::Deserializer<'de>,
300        {
301            deserializer.deserialize_str(MonthVisitor)
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::Month;
309    use crate::{Datelike, Months, OutOfRange, TimeZone, Utc};
310
311    #[test]
312    fn test_month_enum_try_from() {
313        assert_eq!(Month::try_from(1), Ok(Month::January));
314        assert_eq!(Month::try_from(2), Ok(Month::February));
315        assert_eq!(Month::try_from(12), Ok(Month::December));
316        assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
317
318        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
319        assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
320
321        let month = Month::January;
322        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
323        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
324    }
325
326    #[test]
327    fn test_month_enum_primitive_parse() {
328        use num_traits::FromPrimitive;
329
330        let jan_opt = Month::from_u32(1);
331        let feb_opt = Month::from_u64(2);
332        let dec_opt = Month::from_i64(12);
333        let no_month = Month::from_u32(13);
334        assert_eq!(jan_opt, Some(Month::January));
335        assert_eq!(feb_opt, Some(Month::February));
336        assert_eq!(dec_opt, Some(Month::December));
337        assert_eq!(no_month, None);
338
339        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
340        assert_eq!(Month::from_u32(date.month()), Some(Month::October));
341
342        let month = Month::January;
343        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
344        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
345    }
346
347    #[test]
348    fn test_month_enum_succ_pred() {
349        assert_eq!(Month::January.succ(), Month::February);
350        assert_eq!(Month::December.succ(), Month::January);
351        assert_eq!(Month::January.pred(), Month::December);
352        assert_eq!(Month::February.pred(), Month::January);
353    }
354
355    #[test]
356    fn test_month_partial_ord() {
357        assert!(Month::January <= Month::January);
358        assert!(Month::January < Month::February);
359        assert!(Month::January < Month::December);
360        assert!(Month::July >= Month::May);
361        assert!(Month::September > Month::March);
362    }
363
364    #[test]
365    fn test_months_as_u32() {
366        assert_eq!(Months::new(0).as_u32(), 0);
367        assert_eq!(Months::new(1).as_u32(), 1);
368        assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX);
369    }
370
371    #[test]
372    #[cfg(feature = "serde")]
373    fn test_serde_serialize() {
374        use serde_json::to_string;
375        use Month::*;
376
377        let cases: Vec<(Month, &str)> = vec![
378            (January, "\"January\""),
379            (February, "\"February\""),
380            (March, "\"March\""),
381            (April, "\"April\""),
382            (May, "\"May\""),
383            (June, "\"June\""),
384            (July, "\"July\""),
385            (August, "\"August\""),
386            (September, "\"September\""),
387            (October, "\"October\""),
388            (November, "\"November\""),
389            (December, "\"December\""),
390        ];
391
392        for (month, expected_str) in cases {
393            let string = to_string(&month).unwrap();
394            assert_eq!(string, expected_str);
395        }
396    }
397
398    #[test]
399    #[cfg(feature = "serde")]
400    fn test_serde_deserialize() {
401        use serde_json::from_str;
402        use Month::*;
403
404        let cases: Vec<(&str, Month)> = vec![
405            ("\"january\"", January),
406            ("\"jan\"", January),
407            ("\"FeB\"", February),
408            ("\"MAR\"", March),
409            ("\"mar\"", March),
410            ("\"april\"", April),
411            ("\"may\"", May),
412            ("\"june\"", June),
413            ("\"JULY\"", July),
414            ("\"august\"", August),
415            ("\"september\"", September),
416            ("\"October\"", October),
417            ("\"November\"", November),
418            ("\"DECEmbEr\"", December),
419        ];
420
421        for (string, expected_month) in cases {
422            let month = from_str::<Month>(string).unwrap();
423            assert_eq!(month, expected_month);
424        }
425
426        let errors: Vec<&str> =
427            vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
428
429        for string in errors {
430            from_str::<Month>(string).unwrap_err();
431        }
432    }
433
434    #[test]
435    #[cfg(feature = "rkyv-validation")]
436    fn test_rkyv_validation() {
437        let month = Month::January;
438        let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap();
439        assert_eq!(rkyv::from_bytes::<Month>(&bytes).unwrap(), month);
440    }
441}