| 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 | |