chrono/
month.rs

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