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;
7use crate::naive::NaiveDate;
8
9/// The month of the year.
10///
11/// This enum is just a convenience implementation.
12/// The month in dates created by DateLike objects does not return this enum.
13///
14/// It is possible to convert from a date to a month independently
15/// ```
16/// use chrono::prelude::*;
17/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
18/// // `2019-10-28T09:10:11Z`
19/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok();
20/// assert_eq!(month, Some(Month::October))
21/// ```
22/// Or from a Month to an integer usable by dates
23/// ```
24/// # use chrono::prelude::*;
25/// let month = Month::January;
26/// let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
27/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
28/// ```
29/// Allows mapping from and to month, from 1-January to 12-December.
30/// Can be Serialized/Deserialized with serde
31// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior.
32#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)]
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    /// Get the length in days of the month
167    ///
168    /// Yields `None` if `year` is out of range for `NaiveDate`.
169    pub fn num_days(&self, year: i32) -> Option<u8> {
170        Some(match *self {
171            Month::January => 31,
172            Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() {
173                true => 29,
174                false => 28,
175            },
176            Month::March => 31,
177            Month::April => 30,
178            Month::May => 31,
179            Month::June => 30,
180            Month::July => 31,
181            Month::August => 31,
182            Month::September => 30,
183            Month::October => 31,
184            Month::November => 30,
185            Month::December => 31,
186        })
187    }
188}
189
190impl TryFrom<u8> for Month {
191    type Error = OutOfRange;
192
193    fn try_from(value: u8) -> Result<Self, Self::Error> {
194        match value {
195            1 => Ok(Month::January),
196            2 => Ok(Month::February),
197            3 => Ok(Month::March),
198            4 => Ok(Month::April),
199            5 => Ok(Month::May),
200            6 => Ok(Month::June),
201            7 => Ok(Month::July),
202            8 => Ok(Month::August),
203            9 => Ok(Month::September),
204            10 => Ok(Month::October),
205            11 => Ok(Month::November),
206            12 => Ok(Month::December),
207            _ => Err(OutOfRange::new()),
208        }
209    }
210}
211
212impl num_traits::FromPrimitive for Month {
213    /// Returns an `Option<Month>` from a i64, assuming a 1-index, January = 1.
214    ///
215    /// `Month::from_i64(n: i64)`: | `1`                  | `2`                   | ... | `12`
216    /// ---------------------------| -------------------- | --------------------- | ... | -----
217    /// ``:                        | Some(Month::January) | Some(Month::February) | ... | Some(Month::December)
218    #[inline]
219    fn from_u64(n: u64) -> Option<Month> {
220        Self::from_u32(n as u32)
221    }
222
223    #[inline]
224    fn from_i64(n: i64) -> Option<Month> {
225        Self::from_u32(n as u32)
226    }
227
228    #[inline]
229    fn from_u32(n: u32) -> Option<Month> {
230        match n {
231            1 => Some(Month::January),
232            2 => Some(Month::February),
233            3 => Some(Month::March),
234            4 => Some(Month::April),
235            5 => Some(Month::May),
236            6 => Some(Month::June),
237            7 => Some(Month::July),
238            8 => Some(Month::August),
239            9 => Some(Month::September),
240            10 => Some(Month::October),
241            11 => Some(Month::November),
242            12 => Some(Month::December),
243            _ => None,
244        }
245    }
246}
247
248/// A duration in calendar months
249#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
250#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))]
251pub struct Months(pub(crate) u32);
252
253impl Months {
254    /// Construct a new `Months` from a number of months
255    pub const fn new(num: u32) -> Self {
256        Self(num)
257    }
258
259    /// Returns the total number of months in the `Months` instance.
260    #[inline]
261    pub const fn as_u32(&self) -> u32 {
262        self.0
263    }
264}
265
266/// An error resulting from reading `<Month>` value with `FromStr`.
267#[derive(Clone, PartialEq, Eq)]
268pub struct ParseMonthError {
269    pub(crate) _dummy: (),
270}
271
272#[cfg(feature = "std")]
273impl std::error::Error for ParseMonthError {}
274
275impl fmt::Display for ParseMonthError {
276    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277        write!(f, "ParseMonthError {{ .. }}")
278    }
279}
280
281impl fmt::Debug for ParseMonthError {
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283        write!(f, "ParseMonthError {{ .. }}")
284    }
285}
286
287#[cfg(feature = "serde")]
288mod month_serde {
289    use super::Month;
290    use serde::{de, ser};
291
292    use core::fmt;
293
294    impl ser::Serialize for Month {
295        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
296        where
297            S: ser::Serializer,
298        {
299            serializer.collect_str(self.name())
300        }
301    }
302
303    struct MonthVisitor;
304
305    impl de::Visitor<'_> for MonthVisitor {
306        type Value = Month;
307
308        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
309            f.write_str("Month")
310        }
311
312        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
313        where
314            E: de::Error,
315        {
316            value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected"))
317        }
318    }
319
320    impl<'de> de::Deserialize<'de> for Month {
321        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322        where
323            D: de::Deserializer<'de>,
324        {
325            deserializer.deserialize_str(MonthVisitor)
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::Month;
333    use crate::{Datelike, Months, OutOfRange, TimeZone, Utc};
334
335    #[test]
336    fn test_month_enum_try_from() {
337        assert_eq!(Month::try_from(1), Ok(Month::January));
338        assert_eq!(Month::try_from(2), Ok(Month::February));
339        assert_eq!(Month::try_from(12), Ok(Month::December));
340        assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
341
342        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
343        assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
344
345        let month = Month::January;
346        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
347        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
348    }
349
350    #[test]
351    fn test_month_enum_primitive_parse() {
352        use num_traits::FromPrimitive;
353
354        let jan_opt = Month::from_u32(1);
355        let feb_opt = Month::from_u64(2);
356        let dec_opt = Month::from_i64(12);
357        let no_month = Month::from_u32(13);
358        assert_eq!(jan_opt, Some(Month::January));
359        assert_eq!(feb_opt, Some(Month::February));
360        assert_eq!(dec_opt, Some(Month::December));
361        assert_eq!(no_month, None);
362
363        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
364        assert_eq!(Month::from_u32(date.month()), Some(Month::October));
365
366        let month = Month::January;
367        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
368        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
369    }
370
371    #[test]
372    fn test_month_enum_succ_pred() {
373        assert_eq!(Month::January.succ(), Month::February);
374        assert_eq!(Month::December.succ(), Month::January);
375        assert_eq!(Month::January.pred(), Month::December);
376        assert_eq!(Month::February.pred(), Month::January);
377    }
378
379    #[test]
380    fn test_month_partial_ord() {
381        assert!(Month::January <= Month::January);
382        assert!(Month::January < Month::February);
383        assert!(Month::January < Month::December);
384        assert!(Month::July >= Month::May);
385        assert!(Month::September > Month::March);
386    }
387
388    #[test]
389    fn test_months_as_u32() {
390        assert_eq!(Months::new(0).as_u32(), 0);
391        assert_eq!(Months::new(1).as_u32(), 1);
392        assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX);
393    }
394
395    #[test]
396    #[cfg(feature = "serde")]
397    fn test_serde_serialize() {
398        use Month::*;
399        use serde_json::to_string;
400
401        let cases: Vec<(Month, &str)> = vec![
402            (January, "\"January\""),
403            (February, "\"February\""),
404            (March, "\"March\""),
405            (April, "\"April\""),
406            (May, "\"May\""),
407            (June, "\"June\""),
408            (July, "\"July\""),
409            (August, "\"August\""),
410            (September, "\"September\""),
411            (October, "\"October\""),
412            (November, "\"November\""),
413            (December, "\"December\""),
414        ];
415
416        for (month, expected_str) in cases {
417            let string = to_string(&month).unwrap();
418            assert_eq!(string, expected_str);
419        }
420    }
421
422    #[test]
423    #[cfg(feature = "serde")]
424    fn test_serde_deserialize() {
425        use Month::*;
426        use serde_json::from_str;
427
428        let cases: Vec<(&str, Month)> = vec![
429            ("\"january\"", January),
430            ("\"jan\"", January),
431            ("\"FeB\"", February),
432            ("\"MAR\"", March),
433            ("\"mar\"", March),
434            ("\"april\"", April),
435            ("\"may\"", May),
436            ("\"june\"", June),
437            ("\"JULY\"", July),
438            ("\"august\"", August),
439            ("\"september\"", September),
440            ("\"October\"", October),
441            ("\"November\"", November),
442            ("\"DECEmbEr\"", December),
443        ];
444
445        for (string, expected_month) in cases {
446            let month = from_str::<Month>(string).unwrap();
447            assert_eq!(month, expected_month);
448        }
449
450        let errors: Vec<&str> =
451            vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
452
453        for string in errors {
454            from_str::<Month>(string).unwrap_err();
455        }
456    }
457
458    #[test]
459    #[cfg(feature = "rkyv-validation")]
460    fn test_rkyv_validation() {
461        let month = Month::January;
462        let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap();
463        assert_eq!(rkyv::from_bytes::<Month>(&bytes).unwrap(), month);
464    }
465
466    #[test]
467    fn num_days() {
468        assert_eq!(Month::January.num_days(2020), Some(31));
469        assert_eq!(Month::February.num_days(2020), Some(29));
470        assert_eq!(Month::February.num_days(2019), Some(28));
471    }
472}