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