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