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