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