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 | 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 | |
190 | impl 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 | |
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 | 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))] |
251 | pub struct Months(pub(crate) u32); |
252 | |
253 | impl 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)] |
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 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
277 | write!(f, "ParseMonthError {{ .. }}" ) |
278 | } |
279 | } |
280 | |
281 | impl 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" )] |
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 | } |
473 | |