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
195    #[inline]
196    fn from_u64(n: u64) -> Option<Month> {
197        Self::from_u32(n as u32)
198    }
199
200    #[inline]
201    fn from_i64(n: i64) -> Option<Month> {
202        Self::from_u32(n as u32)
203    }
204
205    #[inline]
206    fn from_u32(n: u32) -> Option<Month> {
207        match n {
208            1 => Some(Month::January),
209            2 => Some(Month::February),
210            3 => Some(Month::March),
211            4 => Some(Month::April),
212            5 => Some(Month::May),
213            6 => Some(Month::June),
214            7 => Some(Month::July),
215            8 => Some(Month::August),
216            9 => Some(Month::September),
217            10 => Some(Month::October),
218            11 => Some(Month::November),
219            12 => Some(Month::December),
220            _ => None,
221        }
222    }
223}
224
225/// A duration in calendar months
226#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
227#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
228pub struct Months(pub(crate) u32);
229
230impl Months {
231    /// Construct a new `Months` from a number of months
232    pub const fn new(num: u32) -> Self {
233        Self(num)
234    }
235
236    /// Returns the total number of months in the `Months` instance.
237    #[inline]
238    pub const fn as_u32(&self) -> u32 {
239        self.0
240    }
241}
242
243/// An error resulting from reading `<Month>` value with `FromStr`.
244#[derive(Clone, PartialEq, Eq)]
245pub struct ParseMonthError {
246    pub(crate) _dummy: (),
247}
248
249#[cfg(feature = "std")]
250impl std::error::Error for ParseMonthError {}
251
252impl fmt::Display for ParseMonthError {
253    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
254        write!(f, "ParseMonthError {{ .. }}")
255    }
256}
257
258impl fmt::Debug for ParseMonthError {
259    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260        write!(f, "ParseMonthError {{ .. }}")
261    }
262}
263
264#[cfg(feature = "serde")]
265mod month_serde {
266    use super::Month;
267    use serde::{de, ser};
268
269    use core::fmt;
270
271    impl ser::Serialize for Month {
272        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
273        where
274            S: ser::Serializer,
275        {
276            serializer.collect_str(self.name())
277        }
278    }
279
280    struct MonthVisitor;
281
282    impl<'de> de::Visitor<'de> for MonthVisitor {
283        type Value = Month;
284
285        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
286            f.write_str("Month")
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 (3-letter) or full month names expected"))
294        }
295    }
296
297    impl<'de> de::Deserialize<'de> for Month {
298        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
299        where
300            D: de::Deserializer<'de>,
301        {
302            deserializer.deserialize_str(MonthVisitor)
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::Month;
310    use crate::{Datelike, Months, OutOfRange, TimeZone, Utc};
311
312    #[test]
313    fn test_month_enum_try_from() {
314        assert_eq!(Month::try_from(1), Ok(Month::January));
315        assert_eq!(Month::try_from(2), Ok(Month::February));
316        assert_eq!(Month::try_from(12), Ok(Month::December));
317        assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
318
319        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
320        assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
321
322        let month = Month::January;
323        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
324        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
325    }
326
327    #[test]
328    fn test_month_enum_primitive_parse() {
329        use num_traits::FromPrimitive;
330
331        let jan_opt = Month::from_u32(1);
332        let feb_opt = Month::from_u64(2);
333        let dec_opt = Month::from_i64(12);
334        let no_month = Month::from_u32(13);
335        assert_eq!(jan_opt, Some(Month::January));
336        assert_eq!(feb_opt, Some(Month::February));
337        assert_eq!(dec_opt, Some(Month::December));
338        assert_eq!(no_month, None);
339
340        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
341        assert_eq!(Month::from_u32(date.month()), Some(Month::October));
342
343        let month = Month::January;
344        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
345        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
346    }
347
348    #[test]
349    fn test_month_enum_succ_pred() {
350        assert_eq!(Month::January.succ(), Month::February);
351        assert_eq!(Month::December.succ(), Month::January);
352        assert_eq!(Month::January.pred(), Month::December);
353        assert_eq!(Month::February.pred(), Month::January);
354    }
355
356    #[test]
357    fn test_month_partial_ord() {
358        assert!(Month::January <= Month::January);
359        assert!(Month::January < Month::February);
360        assert!(Month::January < Month::December);
361        assert!(Month::July >= Month::May);
362        assert!(Month::September > Month::March);
363    }
364
365    #[test]
366    fn test_months_as_u32() {
367        assert_eq!(Months::new(0).as_u32(), 0);
368        assert_eq!(Months::new(1).as_u32(), 1);
369        assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX);
370    }
371
372    #[test]
373    #[cfg(feature = "serde")]
374    fn test_serde_serialize() {
375        use serde_json::to_string;
376        use Month::*;
377
378        let cases: Vec<(Month, &str)> = vec![
379            (January, "\"January\""),
380            (February, "\"February\""),
381            (March, "\"March\""),
382            (April, "\"April\""),
383            (May, "\"May\""),
384            (June, "\"June\""),
385            (July, "\"July\""),
386            (August, "\"August\""),
387            (September, "\"September\""),
388            (October, "\"October\""),
389            (November, "\"November\""),
390            (December, "\"December\""),
391        ];
392
393        for (month, expected_str) in cases {
394            let string = to_string(&month).unwrap();
395            assert_eq!(string, expected_str);
396        }
397    }
398
399    #[test]
400    #[cfg(feature = "serde")]
401    fn test_serde_deserialize() {
402        use serde_json::from_str;
403        use Month::*;
404
405        let cases: Vec<(&str, Month)> = vec![
406            ("\"january\"", January),
407            ("\"jan\"", January),
408            ("\"FeB\"", February),
409            ("\"MAR\"", March),
410            ("\"mar\"", March),
411            ("\"april\"", April),
412            ("\"may\"", May),
413            ("\"june\"", June),
414            ("\"JULY\"", July),
415            ("\"august\"", August),
416            ("\"september\"", September),
417            ("\"October\"", October),
418            ("\"November\"", November),
419            ("\"DECEmbEr\"", December),
420        ];
421
422        for (string, expected_month) in cases {
423            let month = from_str::<Month>(string).unwrap();
424            assert_eq!(month, expected_month);
425        }
426
427        let errors: Vec<&str> =
428            vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
429
430        for string in errors {
431            from_str::<Month>(string).unwrap_err();
432        }
433    }
434
435    #[test]
436    #[cfg(feature = "rkyv-validation")]
437    fn test_rkyv_validation() {
438        let month = Month::January;
439        let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap();
440        assert_eq!(rkyv::from_bytes::<Month>(&bytes).unwrap(), month);
441    }
442}