Coverage Report

Created: 2025-08-29 06:34

/rust/registry/src/index.crates.io-6f17d22bba15001f/chrono-0.4.41/src/month.rs
Line
Count
Source (jump to first uncovered line)
1
use core::fmt;
2
3
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
4
use rkyv::{Archive, Deserialize, Serialize};
5
6
use crate::OutOfRange;
7
use 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
pub 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
68
impl Month {
69
    /// The next month.
70
    ///
71
    /// `m`:        | `January`  | `February` | `...` | `December`
72
    /// ----------- | ---------  | ---------- | --- | ---------
73
    /// `m.succ()`: | `February` | `March`    | `...` | `January`
74
    #[inline]
75
    #[must_use]
76
0
    pub const fn succ(&self) -> Month {
77
0
        match *self {
78
0
            Month::January => Month::February,
79
0
            Month::February => Month::March,
80
0
            Month::March => Month::April,
81
0
            Month::April => Month::May,
82
0
            Month::May => Month::June,
83
0
            Month::June => Month::July,
84
0
            Month::July => Month::August,
85
0
            Month::August => Month::September,
86
0
            Month::September => Month::October,
87
0
            Month::October => Month::November,
88
0
            Month::November => Month::December,
89
0
            Month::December => Month::January,
90
        }
91
0
    }
92
93
    /// The previous month.
94
    ///
95
    /// `m`:        | `January`  | `February` | `...` | `December`
96
    /// ----------- | ---------  | ---------- | --- | ---------
97
    /// `m.pred()`: | `December` | `January`  | `...` | `November`
98
    #[inline]
99
    #[must_use]
100
0
    pub const fn pred(&self) -> Month {
101
0
        match *self {
102
0
            Month::January => Month::December,
103
0
            Month::February => Month::January,
104
0
            Month::March => Month::February,
105
0
            Month::April => Month::March,
106
0
            Month::May => Month::April,
107
0
            Month::June => Month::May,
108
0
            Month::July => Month::June,
109
0
            Month::August => Month::July,
110
0
            Month::September => Month::August,
111
0
            Month::October => Month::September,
112
0
            Month::November => Month::October,
113
0
            Month::December => Month::November,
114
        }
115
0
    }
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
0
    pub const fn number_from_month(&self) -> u32 {
125
0
        match *self {
126
0
            Month::January => 1,
127
0
            Month::February => 2,
128
0
            Month::March => 3,
129
0
            Month::April => 4,
130
0
            Month::May => 5,
131
0
            Month::June => 6,
132
0
            Month::July => 7,
133
0
            Month::August => 8,
134
0
            Month::September => 9,
135
0
            Month::October => 10,
136
0
            Month::November => 11,
137
0
            Month::December => 12,
138
        }
139
0
    }
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
0
    pub const fn name(&self) -> &'static str {
150
0
        match *self {
151
0
            Month::January => "January",
152
0
            Month::February => "February",
153
0
            Month::March => "March",
154
0
            Month::April => "April",
155
0
            Month::May => "May",
156
0
            Month::June => "June",
157
0
            Month::July => "July",
158
0
            Month::August => "August",
159
0
            Month::September => "September",
160
0
            Month::October => "October",
161
0
            Month::November => "November",
162
0
            Month::December => "December",
163
        }
164
0
    }
165
166
    /// Get the length in days of the month
167
    ///
168
    /// Yields `None` if `year` is out of range for `NaiveDate`.
169
0
    pub fn num_days(&self, year: i32) -> Option<u8> {
170
0
        Some(match *self {
171
0
            Month::January => 31,
172
0
            Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() {
173
0
                true => 29,
174
0
                false => 28,
175
            },
176
0
            Month::March => 31,
177
0
            Month::April => 30,
178
0
            Month::May => 31,
179
0
            Month::June => 30,
180
0
            Month::July => 31,
181
0
            Month::August => 31,
182
0
            Month::September => 30,
183
0
            Month::October => 31,
184
0
            Month::November => 30,
185
0
            Month::December => 31,
186
        })
187
0
    }
188
}
189
190
impl TryFrom<u8> for Month {
191
    type Error = OutOfRange;
192
193
0
    fn try_from(value: u8) -> Result<Self, Self::Error> {
194
0
        match value {
195
0
            1 => Ok(Month::January),
196
0
            2 => Ok(Month::February),
197
0
            3 => Ok(Month::March),
198
0
            4 => Ok(Month::April),
199
0
            5 => Ok(Month::May),
200
0
            6 => Ok(Month::June),
201
0
            7 => Ok(Month::July),
202
0
            8 => Ok(Month::August),
203
0
            9 => Ok(Month::September),
204
0
            10 => Ok(Month::October),
205
0
            11 => Ok(Month::November),
206
0
            12 => Ok(Month::December),
207
0
            _ => Err(OutOfRange::new()),
208
        }
209
0
    }
210
}
211
212
impl 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
0
    fn from_u64(n: u64) -> Option<Month> {
220
0
        Self::from_u32(n as u32)
221
0
    }
222
223
    #[inline]
224
0
    fn from_i64(n: i64) -> Option<Month> {
225
0
        Self::from_u32(n as u32)
226
0
    }
227
228
    #[inline]
229
0
    fn from_u32(n: u32) -> Option<Month> {
230
0
        match n {
231
0
            1 => Some(Month::January),
232
0
            2 => Some(Month::February),
233
0
            3 => Some(Month::March),
234
0
            4 => Some(Month::April),
235
0
            5 => Some(Month::May),
236
0
            6 => Some(Month::June),
237
0
            7 => Some(Month::July),
238
0
            8 => Some(Month::August),
239
0
            9 => Some(Month::September),
240
0
            10 => Some(Month::October),
241
0
            11 => Some(Month::November),
242
0
            12 => Some(Month::December),
243
0
            _ => None,
244
        }
245
0
    }
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))]
251
pub struct Months(pub(crate) u32);
252
253
impl Months {
254
    /// Construct a new `Months` from a number of months
255
0
    pub const fn new(num: u32) -> Self {
256
0
        Self(num)
257
0
    }
258
259
    /// Returns the total number of months in the `Months` instance.
260
    #[inline]
261
0
    pub const fn as_u32(&self) -> u32 {
262
0
        self.0
263
0
    }
264
}
265
266
/// An error resulting from reading `<Month>` value with `FromStr`.
267
#[derive(Clone, PartialEq, Eq)]
268
pub struct ParseMonthError {
269
    pub(crate) _dummy: (),
270
}
271
272
#[cfg(feature = "std")]
273
impl std::error::Error for ParseMonthError {}
274
275
impl fmt::Display for ParseMonthError {
276
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277
0
        write!(f, "ParseMonthError {{ .. }}")
278
0
    }
279
}
280
281
impl fmt::Debug for ParseMonthError {
282
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283
0
        write!(f, "ParseMonthError {{ .. }}")
284
0
    }
285
}
286
287
#[cfg(feature = "serde")]
288
mod 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)]
331
mod 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
}