Coverage Report

Created: 2026-02-26 06:14

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