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
275#[cfg(all(not(feature = "std"), feature = "core-error"))]
276impl core::error::Error for ParseMonthError {}
277
278impl fmt::Display for ParseMonthError {
279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        write!(f, "ParseMonthError {{ .. }}")
281    }
282}
283
284impl fmt::Debug for ParseMonthError {
285    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286        write!(f, "ParseMonthError {{ .. }}")
287    }
288}
289
290#[cfg(feature = "serde")]
291mod month_serde {
292    use super::Month;
293    use serde::{de, ser};
294
295    use core::fmt;
296
297    impl ser::Serialize for Month {
298        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
299        where
300            S: ser::Serializer,
301        {
302            serializer.collect_str(self.name())
303        }
304    }
305
306    struct MonthVisitor;
307
308    impl de::Visitor<'_> for MonthVisitor {
309        type Value = Month;
310
311        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
312            f.write_str("Month")
313        }
314
315        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
316        where
317            E: de::Error,
318        {
319            value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected"))
320        }
321    }
322
323    impl<'de> de::Deserialize<'de> for Month {
324        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
325        where
326            D: de::Deserializer<'de>,
327        {
328            deserializer.deserialize_str(MonthVisitor)
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::Month;
336    use crate::{Datelike, Months, OutOfRange, TimeZone, Utc};
337
338    #[test]
339    fn test_month_enum_try_from() {
340        assert_eq!(Month::try_from(1), Ok(Month::January));
341        assert_eq!(Month::try_from(2), Ok(Month::February));
342        assert_eq!(Month::try_from(12), Ok(Month::December));
343        assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
344
345        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
346        assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
347
348        let month = Month::January;
349        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
350        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
351    }
352
353    #[test]
354    fn test_month_enum_primitive_parse() {
355        use num_traits::FromPrimitive;
356
357        let jan_opt = Month::from_u32(1);
358        let feb_opt = Month::from_u64(2);
359        let dec_opt = Month::from_i64(12);
360        let no_month = Month::from_u32(13);
361        assert_eq!(jan_opt, Some(Month::January));
362        assert_eq!(feb_opt, Some(Month::February));
363        assert_eq!(dec_opt, Some(Month::December));
364        assert_eq!(no_month, None);
365
366        let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
367        assert_eq!(Month::from_u32(date.month()), Some(Month::October));
368
369        let month = Month::January;
370        let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
371        assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
372    }
373
374    #[test]
375    fn test_month_enum_succ_pred() {
376        assert_eq!(Month::January.succ(), Month::February);
377        assert_eq!(Month::December.succ(), Month::January);
378        assert_eq!(Month::January.pred(), Month::December);
379        assert_eq!(Month::February.pred(), Month::January);
380    }
381
382    #[test]
383    fn test_month_partial_ord() {
384        assert!(Month::January <= Month::January);
385        assert!(Month::January < Month::February);
386        assert!(Month::January < Month::December);
387        assert!(Month::July >= Month::May);
388        assert!(Month::September > Month::March);
389    }
390
391    #[test]
392    fn test_months_as_u32() {
393        assert_eq!(Months::new(0).as_u32(), 0);
394        assert_eq!(Months::new(1).as_u32(), 1);
395        assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX);
396    }
397
398    #[test]
399    #[cfg(feature = "serde")]
400    fn test_serde_serialize() {
401        use Month::*;
402        use serde_json::to_string;
403
404        let cases: Vec<(Month, &str)> = vec![
405            (January, "\"January\""),
406            (February, "\"February\""),
407            (March, "\"March\""),
408            (April, "\"April\""),
409            (May, "\"May\""),
410            (June, "\"June\""),
411            (July, "\"July\""),
412            (August, "\"August\""),
413            (September, "\"September\""),
414            (October, "\"October\""),
415            (November, "\"November\""),
416            (December, "\"December\""),
417        ];
418
419        for (month, expected_str) in cases {
420            let string = to_string(&month).unwrap();
421            assert_eq!(string, expected_str);
422        }
423    }
424
425    #[test]
426    #[cfg(feature = "serde")]
427    fn test_serde_deserialize() {
428        use Month::*;
429        use serde_json::from_str;
430
431        let cases: Vec<(&str, Month)> = vec![
432            ("\"january\"", January),
433            ("\"jan\"", January),
434            ("\"FeB\"", February),
435            ("\"MAR\"", March),
436            ("\"mar\"", March),
437            ("\"april\"", April),
438            ("\"may\"", May),
439            ("\"june\"", June),
440            ("\"JULY\"", July),
441            ("\"august\"", August),
442            ("\"september\"", September),
443            ("\"October\"", October),
444            ("\"November\"", November),
445            ("\"DECEmbEr\"", December),
446        ];
447
448        for (string, expected_month) in cases {
449            let month = from_str::<Month>(string).unwrap();
450            assert_eq!(month, expected_month);
451        }
452
453        let errors: Vec<&str> =
454            vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""];
455
456        for string in errors {
457            from_str::<Month>(string).unwrap_err();
458        }
459    }
460
461    #[test]
462    #[cfg(feature = "rkyv-validation")]
463    fn test_rkyv_validation() {
464        let month = Month::January;
465        let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap();
466        assert_eq!(rkyv::from_bytes::<Month>(&bytes).unwrap(), month);
467    }
468
469    #[test]
470    fn num_days() {
471        assert_eq!(Month::January.num_days(2020), Some(31));
472        assert_eq!(Month::February.num_days(2020), Some(29));
473        assert_eq!(Month::February.num_days(2019), Some(28));
474    }
475}