| 1 | // This is a part of Chrono. |
| 2 | // See README.md and LICENSE.txt for details. |
| 3 | |
| 4 | //! A collection of parsed date and time items. |
| 5 | //! They can be constructed incrementally while being checked for consistency. |
| 6 | |
| 7 | use super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE, ParseResult}; |
| 8 | use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; |
| 9 | use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; |
| 10 | use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; |
| 11 | |
| 12 | /// A type to hold parsed fields of date and time that can check all fields are consistent. |
| 13 | /// |
| 14 | /// There are three classes of methods: |
| 15 | /// |
| 16 | /// - `set_*` methods to set fields you have available. They do a basic range check, and if the |
| 17 | /// same field is set more than once it is checked for consistency. |
| 18 | /// |
| 19 | /// - `to_*` methods try to make a concrete date and time value out of set fields. |
| 20 | /// They fully check that all fields are consistent and whether the date/datetime exists. |
| 21 | /// |
| 22 | /// - Methods to inspect the parsed fields. |
| 23 | /// |
| 24 | /// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it |
| 25 | /// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the |
| 26 | /// results of a string parsed with chrono without converting it to concrete types. |
| 27 | /// |
| 28 | /// # Resolving algorithm |
| 29 | /// |
| 30 | /// Resolving date/time parts is littered with lots of corner cases, which is why common date/time |
| 31 | /// parsers do not implement it correctly. |
| 32 | /// |
| 33 | /// Chrono provides a complete resolution algorithm that checks all fields for consistency via the |
| 34 | /// `Parsed` type. |
| 35 | /// |
| 36 | /// As an easy example, consider RFC 2822. The [RFC 2822 date and time format] has a day of the week |
| 37 | /// part, which should be consistent with the other date parts. But a `strptime`-based parse would |
| 38 | /// happily accept inconsistent input: |
| 39 | /// |
| 40 | /// ```python |
| 41 | /// >>> import time |
| 42 | /// >>> time.strptime('Wed, 31 Dec 2014 04:26:40 +0000', |
| 43 | /// '%a, %d %b %Y %H:%M:%S +0000') |
| 44 | /// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, |
| 45 | /// tm_hour=4, tm_min=26, tm_sec=40, |
| 46 | /// tm_wday=2, tm_yday=365, tm_isdst=-1) |
| 47 | /// >>> time.strptime('Thu, 31 Dec 2014 04:26:40 +0000', |
| 48 | /// '%a, %d %b %Y %H:%M:%S +0000') |
| 49 | /// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, |
| 50 | /// tm_hour=4, tm_min=26, tm_sec=40, |
| 51 | /// tm_wday=3, tm_yday=365, tm_isdst=-1) |
| 52 | /// ``` |
| 53 | /// |
| 54 | /// [RFC 2822 date and time format]: https://tools.ietf.org/html/rfc2822#section-3.3 |
| 55 | /// |
| 56 | /// # Example |
| 57 | /// |
| 58 | /// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent. |
| 59 | /// |
| 60 | /// ``` |
| 61 | /// # #[cfg (feature = "alloc" )] { |
| 62 | /// use chrono::format::{ParseErrorKind, Parsed}; |
| 63 | /// use chrono::Weekday; |
| 64 | /// |
| 65 | /// let mut parsed = Parsed::new(); |
| 66 | /// parsed.set_weekday(Weekday::Wed)?; |
| 67 | /// parsed.set_day(31)?; |
| 68 | /// parsed.set_month(12)?; |
| 69 | /// parsed.set_year(2014)?; |
| 70 | /// parsed.set_hour(4)?; |
| 71 | /// parsed.set_minute(26)?; |
| 72 | /// parsed.set_second(40)?; |
| 73 | /// parsed.set_offset(0)?; |
| 74 | /// let dt = parsed.to_datetime()?; |
| 75 | /// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000" ); |
| 76 | /// |
| 77 | /// let mut parsed = Parsed::new(); |
| 78 | /// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day |
| 79 | /// parsed.set_day(31)?; |
| 80 | /// parsed.set_month(12)?; |
| 81 | /// parsed.set_year(2014)?; |
| 82 | /// parsed.set_hour(4)?; |
| 83 | /// parsed.set_minute(26)?; |
| 84 | /// parsed.set_second(40)?; |
| 85 | /// parsed.set_offset(0)?; |
| 86 | /// let result = parsed.to_datetime(); |
| 87 | /// |
| 88 | /// assert!(result.is_err()); |
| 89 | /// if let Err(error) = result { |
| 90 | /// assert_eq!(error.kind(), ParseErrorKind::Impossible); |
| 91 | /// } |
| 92 | /// # } |
| 93 | /// # Ok::<(), chrono::ParseError>(()) |
| 94 | /// ``` |
| 95 | /// |
| 96 | /// The same using chrono's build-in parser for RFC 2822 (the [RFC2822 formatting item]) and |
| 97 | /// [`format::parse()`] showing how to inspect a field on failure. |
| 98 | /// |
| 99 | /// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 |
| 100 | /// [`format::parse()`]: crate::format::parse() |
| 101 | /// |
| 102 | /// ``` |
| 103 | /// # #[cfg (feature = "alloc" )] { |
| 104 | /// use chrono::format::{parse, Fixed, Item, Parsed}; |
| 105 | /// use chrono::Weekday; |
| 106 | /// |
| 107 | /// let rfc_2822 = [Item::Fixed(Fixed::RFC2822)]; |
| 108 | /// |
| 109 | /// let mut parsed = Parsed::new(); |
| 110 | /// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000" , rfc_2822.iter())?; |
| 111 | /// let dt = parsed.to_datetime()?; |
| 112 | /// |
| 113 | /// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000" ); |
| 114 | /// |
| 115 | /// let mut parsed = Parsed::new(); |
| 116 | /// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000" , rfc_2822.iter())?; |
| 117 | /// let result = parsed.to_datetime(); |
| 118 | /// |
| 119 | /// assert!(result.is_err()); |
| 120 | /// if result.is_err() { |
| 121 | /// // What is the weekday? |
| 122 | /// assert_eq!(parsed.weekday(), Some(Weekday::Thu)); |
| 123 | /// } |
| 124 | /// # } |
| 125 | /// # Ok::<(), chrono::ParseError>(()) |
| 126 | /// ``` |
| 127 | #[allow (clippy::manual_non_exhaustive)] |
| 128 | #[derive (Clone, PartialEq, Eq, Debug, Default, Hash)] |
| 129 | pub struct Parsed { |
| 130 | #[doc (hidden)] |
| 131 | pub year: Option<i32>, |
| 132 | #[doc (hidden)] |
| 133 | pub year_div_100: Option<i32>, |
| 134 | #[doc (hidden)] |
| 135 | pub year_mod_100: Option<i32>, |
| 136 | #[doc (hidden)] |
| 137 | pub isoyear: Option<i32>, |
| 138 | #[doc (hidden)] |
| 139 | pub isoyear_div_100: Option<i32>, |
| 140 | #[doc (hidden)] |
| 141 | pub isoyear_mod_100: Option<i32>, |
| 142 | #[doc (hidden)] |
| 143 | pub quarter: Option<u32>, |
| 144 | #[doc (hidden)] |
| 145 | pub month: Option<u32>, |
| 146 | #[doc (hidden)] |
| 147 | pub week_from_sun: Option<u32>, |
| 148 | #[doc (hidden)] |
| 149 | pub week_from_mon: Option<u32>, |
| 150 | #[doc (hidden)] |
| 151 | pub isoweek: Option<u32>, |
| 152 | #[doc (hidden)] |
| 153 | pub weekday: Option<Weekday>, |
| 154 | #[doc (hidden)] |
| 155 | pub ordinal: Option<u32>, |
| 156 | #[doc (hidden)] |
| 157 | pub day: Option<u32>, |
| 158 | #[doc (hidden)] |
| 159 | pub hour_div_12: Option<u32>, |
| 160 | #[doc (hidden)] |
| 161 | pub hour_mod_12: Option<u32>, |
| 162 | #[doc (hidden)] |
| 163 | pub minute: Option<u32>, |
| 164 | #[doc (hidden)] |
| 165 | pub second: Option<u32>, |
| 166 | #[doc (hidden)] |
| 167 | pub nanosecond: Option<u32>, |
| 168 | #[doc (hidden)] |
| 169 | pub timestamp: Option<i64>, |
| 170 | #[doc (hidden)] |
| 171 | pub offset: Option<i32>, |
| 172 | #[doc (hidden)] |
| 173 | _dummy: (), |
| 174 | } |
| 175 | |
| 176 | /// Checks if `old` is either empty or has the same value as `new` (i.e. "consistent"), |
| 177 | /// and if it is empty, set `old` to `new` as well. |
| 178 | #[inline ] |
| 179 | fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<()> { |
| 180 | match old { |
| 181 | Some(old: &mut T) if *old != new => Err(IMPOSSIBLE), |
| 182 | _ => { |
| 183 | *old = Some(new); |
| 184 | Ok(()) |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | impl Parsed { |
| 190 | /// Returns the initial value of parsed parts. |
| 191 | #[must_use ] |
| 192 | pub fn new() -> Parsed { |
| 193 | Parsed::default() |
| 194 | } |
| 195 | |
| 196 | /// Set the [`year`](Parsed::year) field to the given value. |
| 197 | /// |
| 198 | /// The value can be negative, unlike the [`year_div_100`](Parsed::year_div_100) and |
| 199 | /// [`year_mod_100`](Parsed::year_mod_100) fields. |
| 200 | /// |
| 201 | /// # Errors |
| 202 | /// |
| 203 | /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. |
| 204 | /// |
| 205 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 206 | #[inline ] |
| 207 | pub fn set_year(&mut self, value: i64) -> ParseResult<()> { |
| 208 | set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
| 209 | } |
| 210 | |
| 211 | /// Set the [`year_div_100`](Parsed::year_div_100) field to the given value. |
| 212 | /// |
| 213 | /// # Errors |
| 214 | /// |
| 215 | /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. |
| 216 | /// |
| 217 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 218 | #[inline ] |
| 219 | pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { |
| 220 | if !(0..=i32::MAX as i64).contains(&value) { |
| 221 | return Err(OUT_OF_RANGE); |
| 222 | } |
| 223 | set_if_consistent(&mut self.year_div_100, value as i32) |
| 224 | } |
| 225 | |
| 226 | /// Set the [`year_mod_100`](Parsed::year_mod_100) field to the given value. |
| 227 | /// |
| 228 | /// When set it implies that the year is not negative. |
| 229 | /// |
| 230 | /// If this field is set while the [`year_div_100`](Parsed::year_div_100) field is missing (and |
| 231 | /// the full [`year`](Parsed::year) field is also not set), it assumes a default value for the |
| 232 | /// [`year_div_100`](Parsed::year_div_100) field. |
| 233 | /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. |
| 234 | /// |
| 235 | /// # Errors |
| 236 | /// |
| 237 | /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. |
| 238 | /// |
| 239 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 240 | #[inline ] |
| 241 | pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { |
| 242 | if !(0..100).contains(&value) { |
| 243 | return Err(OUT_OF_RANGE); |
| 244 | } |
| 245 | set_if_consistent(&mut self.year_mod_100, value as i32) |
| 246 | } |
| 247 | |
| 248 | /// Set the [`isoyear`](Parsed::isoyear) field, that is part of an [ISO 8601 week date], to the |
| 249 | /// given value. |
| 250 | /// |
| 251 | /// The value can be negative, unlike the [`isoyear_div_100`](Parsed::isoyear_div_100) and |
| 252 | /// [`isoyear_mod_100`](Parsed::isoyear_mod_100) fields. |
| 253 | /// |
| 254 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 255 | /// |
| 256 | /// # Errors |
| 257 | /// |
| 258 | /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. |
| 259 | /// |
| 260 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 261 | #[inline ] |
| 262 | pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { |
| 263 | set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
| 264 | } |
| 265 | |
| 266 | /// Set the [`isoyear_div_100`](Parsed::isoyear_div_100) field, that is part of an |
| 267 | /// [ISO 8601 week date], to the given value. |
| 268 | /// |
| 269 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 270 | /// |
| 271 | /// # Errors |
| 272 | /// |
| 273 | /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. |
| 274 | /// |
| 275 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 276 | #[inline ] |
| 277 | pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { |
| 278 | if !(0..=i32::MAX as i64).contains(&value) { |
| 279 | return Err(OUT_OF_RANGE); |
| 280 | } |
| 281 | set_if_consistent(&mut self.isoyear_div_100, value as i32) |
| 282 | } |
| 283 | |
| 284 | /// Set the [`isoyear_mod_100`](Parsed::isoyear_mod_100) field, that is part of an |
| 285 | /// [ISO 8601 week date], to the given value. |
| 286 | /// |
| 287 | /// When set it implies that the year is not negative. |
| 288 | /// |
| 289 | /// If this field is set while the [`isoyear_div_100`](Parsed::isoyear_div_100) field is missing |
| 290 | /// (and the full [`isoyear`](Parsed::isoyear) field is also not set), it assumes a default |
| 291 | /// value for the [`isoyear_div_100`](Parsed::isoyear_div_100) field. |
| 292 | /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. |
| 293 | /// |
| 294 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 295 | /// |
| 296 | /// # Errors |
| 297 | /// |
| 298 | /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. |
| 299 | /// |
| 300 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 301 | #[inline ] |
| 302 | pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { |
| 303 | if !(0..100).contains(&value) { |
| 304 | return Err(OUT_OF_RANGE); |
| 305 | } |
| 306 | set_if_consistent(&mut self.isoyear_mod_100, value as i32) |
| 307 | } |
| 308 | |
| 309 | /// Set the [`quarter`](Parsed::quarter) field to the given value. |
| 310 | /// |
| 311 | /// Quarter 1 starts in January. |
| 312 | /// |
| 313 | /// # Errors |
| 314 | /// |
| 315 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-4. |
| 316 | /// |
| 317 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 318 | #[inline ] |
| 319 | pub fn set_quarter(&mut self, value: i64) -> ParseResult<()> { |
| 320 | if !(1..=4).contains(&value) { |
| 321 | return Err(OUT_OF_RANGE); |
| 322 | } |
| 323 | set_if_consistent(&mut self.quarter, value as u32) |
| 324 | } |
| 325 | |
| 326 | /// Set the [`month`](Parsed::month) field to the given value. |
| 327 | /// |
| 328 | /// # Errors |
| 329 | /// |
| 330 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. |
| 331 | /// |
| 332 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 333 | #[inline ] |
| 334 | pub fn set_month(&mut self, value: i64) -> ParseResult<()> { |
| 335 | if !(1..=12).contains(&value) { |
| 336 | return Err(OUT_OF_RANGE); |
| 337 | } |
| 338 | set_if_consistent(&mut self.month, value as u32) |
| 339 | } |
| 340 | |
| 341 | /// Set the [`week_from_sun`](Parsed::week_from_sun) week number field to the given value. |
| 342 | /// |
| 343 | /// Week 1 starts at the first Sunday of January. |
| 344 | /// |
| 345 | /// # Errors |
| 346 | /// |
| 347 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. |
| 348 | /// |
| 349 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 350 | #[inline ] |
| 351 | pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { |
| 352 | if !(0..=53).contains(&value) { |
| 353 | return Err(OUT_OF_RANGE); |
| 354 | } |
| 355 | set_if_consistent(&mut self.week_from_sun, value as u32) |
| 356 | } |
| 357 | |
| 358 | /// Set the [`week_from_mon`](Parsed::week_from_mon) week number field to the given value. |
| 359 | /// Set the 'week number starting with Monday' field to the given value. |
| 360 | /// |
| 361 | /// Week 1 starts at the first Monday of January. |
| 362 | /// |
| 363 | /// # Errors |
| 364 | /// |
| 365 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. |
| 366 | /// |
| 367 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 368 | #[inline ] |
| 369 | pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { |
| 370 | if !(0..=53).contains(&value) { |
| 371 | return Err(OUT_OF_RANGE); |
| 372 | } |
| 373 | set_if_consistent(&mut self.week_from_mon, value as u32) |
| 374 | } |
| 375 | |
| 376 | /// Set the [`isoweek`](Parsed::isoweek) field for an [ISO 8601 week date] to the given value. |
| 377 | /// |
| 378 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 379 | /// |
| 380 | /// # Errors |
| 381 | /// |
| 382 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-53. |
| 383 | /// |
| 384 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 385 | #[inline ] |
| 386 | pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { |
| 387 | if !(1..=53).contains(&value) { |
| 388 | return Err(OUT_OF_RANGE); |
| 389 | } |
| 390 | set_if_consistent(&mut self.isoweek, value as u32) |
| 391 | } |
| 392 | |
| 393 | /// Set the [`weekday`](Parsed::weekday) field to the given value. |
| 394 | /// |
| 395 | /// # Errors |
| 396 | /// |
| 397 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 398 | #[inline ] |
| 399 | pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { |
| 400 | set_if_consistent(&mut self.weekday, value) |
| 401 | } |
| 402 | |
| 403 | /// Set the [`ordinal`](Parsed::ordinal) (day of the year) field to the given value. |
| 404 | /// |
| 405 | /// # Errors |
| 406 | /// |
| 407 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-366. |
| 408 | /// |
| 409 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 410 | #[inline ] |
| 411 | pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { |
| 412 | if !(1..=366).contains(&value) { |
| 413 | return Err(OUT_OF_RANGE); |
| 414 | } |
| 415 | set_if_consistent(&mut self.ordinal, value as u32) |
| 416 | } |
| 417 | |
| 418 | /// Set the [`day`](Parsed::day) of the month field to the given value. |
| 419 | /// |
| 420 | /// # Errors |
| 421 | /// |
| 422 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-31. |
| 423 | /// |
| 424 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 425 | #[inline ] |
| 426 | pub fn set_day(&mut self, value: i64) -> ParseResult<()> { |
| 427 | if !(1..=31).contains(&value) { |
| 428 | return Err(OUT_OF_RANGE); |
| 429 | } |
| 430 | set_if_consistent(&mut self.day, value as u32) |
| 431 | } |
| 432 | |
| 433 | /// Set the [`hour_div_12`](Parsed::hour_div_12) am/pm field to the given value. |
| 434 | /// |
| 435 | /// `false` indicates AM and `true` indicates PM. |
| 436 | /// |
| 437 | /// # Errors |
| 438 | /// |
| 439 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 440 | #[inline ] |
| 441 | pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { |
| 442 | set_if_consistent(&mut self.hour_div_12, value as u32) |
| 443 | } |
| 444 | |
| 445 | /// Set the [`hour_mod_12`](Parsed::hour_mod_12) field, for the hour number in 12-hour clocks, |
| 446 | /// to the given value. |
| 447 | /// |
| 448 | /// Value must be in the canonical range of 1-12. |
| 449 | /// It will internally be stored as 0-11 (`value % 12`). |
| 450 | /// |
| 451 | /// # Errors |
| 452 | /// |
| 453 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. |
| 454 | /// |
| 455 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 456 | #[inline ] |
| 457 | pub fn set_hour12(&mut self, mut value: i64) -> ParseResult<()> { |
| 458 | if !(1..=12).contains(&value) { |
| 459 | return Err(OUT_OF_RANGE); |
| 460 | } |
| 461 | if value == 12 { |
| 462 | value = 0 |
| 463 | } |
| 464 | set_if_consistent(&mut self.hour_mod_12, value as u32) |
| 465 | } |
| 466 | |
| 467 | /// Set the [`hour_div_12`](Parsed::hour_div_12) and [`hour_mod_12`](Parsed::hour_mod_12) |
| 468 | /// fields to the given value for a 24-hour clock. |
| 469 | /// |
| 470 | /// # Errors |
| 471 | /// |
| 472 | /// May return `OUT_OF_RANGE` if `value` is not in the range 0-23. |
| 473 | /// Currently only checks the value is not out of range for a `u32`. |
| 474 | /// |
| 475 | /// Returns `IMPOSSIBLE` one of the fields was already set to a different value. |
| 476 | #[inline ] |
| 477 | pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { |
| 478 | let (hour_div_12, hour_mod_12) = match value { |
| 479 | hour @ 0..=11 => (0, hour as u32), |
| 480 | hour @ 12..=23 => (1, hour as u32 - 12), |
| 481 | _ => return Err(OUT_OF_RANGE), |
| 482 | }; |
| 483 | set_if_consistent(&mut self.hour_div_12, hour_div_12)?; |
| 484 | set_if_consistent(&mut self.hour_mod_12, hour_mod_12) |
| 485 | } |
| 486 | |
| 487 | /// Set the [`minute`](Parsed::minute) field to the given value. |
| 488 | /// |
| 489 | /// # Errors |
| 490 | /// |
| 491 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-59. |
| 492 | /// |
| 493 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 494 | #[inline ] |
| 495 | pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { |
| 496 | if !(0..=59).contains(&value) { |
| 497 | return Err(OUT_OF_RANGE); |
| 498 | } |
| 499 | set_if_consistent(&mut self.minute, value as u32) |
| 500 | } |
| 501 | |
| 502 | /// Set the [`second`](Parsed::second) field to the given value. |
| 503 | /// |
| 504 | /// The value can be 60 in the case of a leap second. |
| 505 | /// |
| 506 | /// # Errors |
| 507 | /// |
| 508 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-60. |
| 509 | /// |
| 510 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 511 | #[inline ] |
| 512 | pub fn set_second(&mut self, value: i64) -> ParseResult<()> { |
| 513 | if !(0..=60).contains(&value) { |
| 514 | return Err(OUT_OF_RANGE); |
| 515 | } |
| 516 | set_if_consistent(&mut self.second, value as u32) |
| 517 | } |
| 518 | |
| 519 | /// Set the [`nanosecond`](Parsed::nanosecond) field to the given value. |
| 520 | /// |
| 521 | /// This is the number of nanoseconds since the whole second. |
| 522 | /// |
| 523 | /// # Errors |
| 524 | /// |
| 525 | /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. |
| 526 | /// |
| 527 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 528 | #[inline ] |
| 529 | pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { |
| 530 | if !(0..=999_999_999).contains(&value) { |
| 531 | return Err(OUT_OF_RANGE); |
| 532 | } |
| 533 | set_if_consistent(&mut self.nanosecond, value as u32) |
| 534 | } |
| 535 | |
| 536 | /// Set the [`timestamp`](Parsed::timestamp) field to the given value. |
| 537 | /// |
| 538 | /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on |
| 539 | /// January 1, 1970. |
| 540 | /// |
| 541 | /// # Errors |
| 542 | /// |
| 543 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 544 | #[inline ] |
| 545 | pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { |
| 546 | set_if_consistent(&mut self.timestamp, value) |
| 547 | } |
| 548 | |
| 549 | /// Set the [`offset`](Parsed::offset) field to the given value. |
| 550 | /// |
| 551 | /// The offset is in seconds from local time to UTC. |
| 552 | /// |
| 553 | /// # Errors |
| 554 | /// |
| 555 | /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. |
| 556 | /// |
| 557 | /// Returns `IMPOSSIBLE` if this field was already set to a different value. |
| 558 | #[inline ] |
| 559 | pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { |
| 560 | set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
| 561 | } |
| 562 | |
| 563 | /// Returns a parsed naive date out of given fields. |
| 564 | /// |
| 565 | /// This method is able to determine the date from given subset of fields: |
| 566 | /// |
| 567 | /// - Year, month, day. |
| 568 | /// - Year, day of the year (ordinal). |
| 569 | /// - Year, week number counted from Sunday or Monday, day of the week. |
| 570 | /// - ISO week date. |
| 571 | /// |
| 572 | /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, |
| 573 | /// the two-digit year is used to guess the century number then. |
| 574 | /// |
| 575 | /// It checks all given date fields are consistent with each other. |
| 576 | /// |
| 577 | /// # Errors |
| 578 | /// |
| 579 | /// This method returns: |
| 580 | /// - `IMPOSSIBLE` if any of the date fields conflict. |
| 581 | /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete date. |
| 582 | /// - `OUT_OF_RANGE` |
| 583 | /// - if any of the date fields of `Parsed` are set to a value beyond their acceptable range. |
| 584 | /// - if the value would be outside the range of a [`NaiveDate`]. |
| 585 | /// - if the date does not exist. |
| 586 | pub fn to_naive_date(&self) -> ParseResult<NaiveDate> { |
| 587 | fn resolve_year( |
| 588 | y: Option<i32>, |
| 589 | q: Option<i32>, |
| 590 | r: Option<i32>, |
| 591 | ) -> ParseResult<Option<i32>> { |
| 592 | match (y, q, r) { |
| 593 | // if there is no further information, simply return the given full year. |
| 594 | // this is a common case, so let's avoid division here. |
| 595 | (y, None, None) => Ok(y), |
| 596 | |
| 597 | // if there is a full year *and* also quotient and/or modulo, |
| 598 | // check if present quotient and/or modulo is consistent to the full year. |
| 599 | // since the presence of those fields means a positive full year, |
| 600 | // we should filter a negative full year first. |
| 601 | (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { |
| 602 | if y < 0 { |
| 603 | return Err(IMPOSSIBLE); |
| 604 | } |
| 605 | let q_ = y / 100; |
| 606 | let r_ = y % 100; |
| 607 | if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { |
| 608 | Ok(Some(y)) |
| 609 | } else { |
| 610 | Err(IMPOSSIBLE) |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | // the full year is missing but we have quotient and modulo. |
| 615 | // reconstruct the full year. make sure that the result is always positive. |
| 616 | (None, Some(q), Some(r @ 0..=99)) => { |
| 617 | if q < 0 { |
| 618 | return Err(IMPOSSIBLE); |
| 619 | } |
| 620 | let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); |
| 621 | Ok(Some(y.ok_or(OUT_OF_RANGE)?)) |
| 622 | } |
| 623 | |
| 624 | // we only have modulo. try to interpret a modulo as a conventional two-digit year. |
| 625 | // note: we are affected by Rust issue #18060. avoid multiple range patterns. |
| 626 | (None, None, Some(r @ 0..=99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), |
| 627 | |
| 628 | // otherwise it is an out-of-bound or insufficient condition. |
| 629 | (None, Some(_), None) => Err(NOT_ENOUGH), |
| 630 | (_, _, Some(_)) => Err(OUT_OF_RANGE), |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | let given_year = resolve_year(self.year, self.year_div_100, self.year_mod_100)?; |
| 635 | let given_isoyear = resolve_year(self.isoyear, self.isoyear_div_100, self.isoyear_mod_100)?; |
| 636 | |
| 637 | // verify the normal year-month-day date. |
| 638 | let verify_ymd = |date: NaiveDate| { |
| 639 | let year = date.year(); |
| 640 | let (year_div_100, year_mod_100) = if year >= 0 { |
| 641 | (Some(year / 100), Some(year % 100)) |
| 642 | } else { |
| 643 | (None, None) // they should be empty to be consistent |
| 644 | }; |
| 645 | let month = date.month(); |
| 646 | let day = date.day(); |
| 647 | self.year.unwrap_or(year) == year |
| 648 | && self.year_div_100.or(year_div_100) == year_div_100 |
| 649 | && self.year_mod_100.or(year_mod_100) == year_mod_100 |
| 650 | && self.month.unwrap_or(month) == month |
| 651 | && self.day.unwrap_or(day) == day |
| 652 | }; |
| 653 | |
| 654 | // verify the ISO week date. |
| 655 | let verify_isoweekdate = |date: NaiveDate| { |
| 656 | let week = date.iso_week(); |
| 657 | let isoyear = week.year(); |
| 658 | let isoweek = week.week(); |
| 659 | let weekday = date.weekday(); |
| 660 | let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { |
| 661 | (Some(isoyear / 100), Some(isoyear % 100)) |
| 662 | } else { |
| 663 | (None, None) // they should be empty to be consistent |
| 664 | }; |
| 665 | self.isoyear.unwrap_or(isoyear) == isoyear |
| 666 | && self.isoyear_div_100.or(isoyear_div_100) == isoyear_div_100 |
| 667 | && self.isoyear_mod_100.or(isoyear_mod_100) == isoyear_mod_100 |
| 668 | && self.isoweek.unwrap_or(isoweek) == isoweek |
| 669 | && self.weekday.unwrap_or(weekday) == weekday |
| 670 | }; |
| 671 | |
| 672 | // verify the ordinal and other (non-ISO) week dates. |
| 673 | let verify_ordinal = |date: NaiveDate| { |
| 674 | let ordinal = date.ordinal(); |
| 675 | let week_from_sun = date.weeks_from(Weekday::Sun); |
| 676 | let week_from_mon = date.weeks_from(Weekday::Mon); |
| 677 | self.ordinal.unwrap_or(ordinal) == ordinal |
| 678 | && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun |
| 679 | && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon |
| 680 | }; |
| 681 | |
| 682 | // test several possibilities. |
| 683 | // tries to construct a full `NaiveDate` as much as possible, then verifies that |
| 684 | // it is consistent with other given fields. |
| 685 | let (verified, parsed_date) = match (given_year, given_isoyear, self) { |
| 686 | (Some(year), _, &Parsed { month: Some(month), day: Some(day), .. }) => { |
| 687 | // year, month, day |
| 688 | let date = NaiveDate::from_ymd_opt(year, month, day).ok_or(OUT_OF_RANGE)?; |
| 689 | (verify_isoweekdate(date) && verify_ordinal(date), date) |
| 690 | } |
| 691 | |
| 692 | (Some(year), _, &Parsed { ordinal: Some(ordinal), .. }) => { |
| 693 | // year, day of the year |
| 694 | let date = NaiveDate::from_yo_opt(year, ordinal).ok_or(OUT_OF_RANGE)?; |
| 695 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
| 696 | } |
| 697 | |
| 698 | (Some(year), _, &Parsed { week_from_sun: Some(week), weekday: Some(weekday), .. }) => { |
| 699 | // year, week (starting at 1st Sunday), day of the week |
| 700 | let date = resolve_week_date(year, week, weekday, Weekday::Sun)?; |
| 701 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
| 702 | } |
| 703 | |
| 704 | (Some(year), _, &Parsed { week_from_mon: Some(week), weekday: Some(weekday), .. }) => { |
| 705 | // year, week (starting at 1st Monday), day of the week |
| 706 | let date = resolve_week_date(year, week, weekday, Weekday::Mon)?; |
| 707 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
| 708 | } |
| 709 | |
| 710 | (_, Some(isoyear), &Parsed { isoweek: Some(isoweek), weekday: Some(weekday), .. }) => { |
| 711 | // ISO year, week, day of the week |
| 712 | let date = NaiveDate::from_isoywd_opt(isoyear, isoweek, weekday); |
| 713 | let date = date.ok_or(OUT_OF_RANGE)?; |
| 714 | (verify_ymd(date) && verify_ordinal(date), date) |
| 715 | } |
| 716 | |
| 717 | (_, _, _) => return Err(NOT_ENOUGH), |
| 718 | }; |
| 719 | |
| 720 | if !verified { |
| 721 | return Err(IMPOSSIBLE); |
| 722 | } else if let Some(parsed) = self.quarter { |
| 723 | if parsed != parsed_date.quarter() { |
| 724 | return Err(IMPOSSIBLE); |
| 725 | } |
| 726 | } |
| 727 | |
| 728 | Ok(parsed_date) |
| 729 | } |
| 730 | |
| 731 | /// Returns a parsed naive time out of given fields. |
| 732 | /// |
| 733 | /// This method is able to determine the time from given subset of fields: |
| 734 | /// |
| 735 | /// - Hour, minute. (second and nanosecond assumed to be 0) |
| 736 | /// - Hour, minute, second. (nanosecond assumed to be 0) |
| 737 | /// - Hour, minute, second, nanosecond. |
| 738 | /// |
| 739 | /// It is able to handle leap seconds when given second is 60. |
| 740 | /// |
| 741 | /// # Errors |
| 742 | /// |
| 743 | /// This method returns: |
| 744 | /// - `OUT_OF_RANGE` if any of the time fields of `Parsed` are set to a value beyond |
| 745 | /// their acceptable range. |
| 746 | /// - `NOT_ENOUGH` if an hour field is missing, if AM/PM is missing in a 12-hour clock, |
| 747 | /// if minutes are missing, or if seconds are missing while the nanosecond field is present. |
| 748 | pub fn to_naive_time(&self) -> ParseResult<NaiveTime> { |
| 749 | let hour_div_12 = match self.hour_div_12 { |
| 750 | Some(v @ 0..=1) => v, |
| 751 | Some(_) => return Err(OUT_OF_RANGE), |
| 752 | None => return Err(NOT_ENOUGH), |
| 753 | }; |
| 754 | let hour_mod_12 = match self.hour_mod_12 { |
| 755 | Some(v @ 0..=11) => v, |
| 756 | Some(_) => return Err(OUT_OF_RANGE), |
| 757 | None => return Err(NOT_ENOUGH), |
| 758 | }; |
| 759 | let hour = hour_div_12 * 12 + hour_mod_12; |
| 760 | |
| 761 | let minute = match self.minute { |
| 762 | Some(v @ 0..=59) => v, |
| 763 | Some(_) => return Err(OUT_OF_RANGE), |
| 764 | None => return Err(NOT_ENOUGH), |
| 765 | }; |
| 766 | |
| 767 | // we allow omitting seconds or nanoseconds, but they should be in the range. |
| 768 | let (second, mut nano) = match self.second.unwrap_or(0) { |
| 769 | v @ 0..=59 => (v, 0), |
| 770 | 60 => (59, 1_000_000_000), |
| 771 | _ => return Err(OUT_OF_RANGE), |
| 772 | }; |
| 773 | nano += match self.nanosecond { |
| 774 | Some(v @ 0..=999_999_999) if self.second.is_some() => v, |
| 775 | Some(0..=999_999_999) => return Err(NOT_ENOUGH), // second is missing |
| 776 | Some(_) => return Err(OUT_OF_RANGE), |
| 777 | None => 0, |
| 778 | }; |
| 779 | |
| 780 | NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) |
| 781 | } |
| 782 | |
| 783 | /// Returns a parsed naive date and time out of given fields, except for the offset field. |
| 784 | /// |
| 785 | /// The offset is assumed to have a given value. It is not compared against the offset field set |
| 786 | /// in the `Parsed` type, so it is allowed to be inconsistent. |
| 787 | /// |
| 788 | /// This method is able to determine the combined date and time from date and time fields or |
| 789 | /// from a single timestamp field. It checks all fields are consistent with each other. |
| 790 | /// |
| 791 | /// # Errors |
| 792 | /// |
| 793 | /// This method returns: |
| 794 | /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of |
| 795 | /// the other fields. |
| 796 | /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime. |
| 797 | /// - `OUT_OF_RANGE` |
| 798 | /// - if any of the date or time fields of `Parsed` are set to a value beyond their acceptable |
| 799 | /// range. |
| 800 | /// - if the value would be outside the range of a [`NaiveDateTime`]. |
| 801 | /// - if the date does not exist. |
| 802 | pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult<NaiveDateTime> { |
| 803 | let date = self.to_naive_date(); |
| 804 | let time = self.to_naive_time(); |
| 805 | if let (Ok(date), Ok(time)) = (date, time) { |
| 806 | let datetime = date.and_time(time); |
| 807 | |
| 808 | // verify the timestamp field if any |
| 809 | // the following is safe, `timestamp` is very limited in range |
| 810 | let timestamp = datetime.and_utc().timestamp() - i64::from(offset); |
| 811 | if let Some(given_timestamp) = self.timestamp { |
| 812 | // if `datetime` represents a leap second, it might be off by one second. |
| 813 | if given_timestamp != timestamp |
| 814 | && !(datetime.nanosecond() >= 1_000_000_000 && given_timestamp == timestamp + 1) |
| 815 | { |
| 816 | return Err(IMPOSSIBLE); |
| 817 | } |
| 818 | } |
| 819 | |
| 820 | Ok(datetime) |
| 821 | } else if let Some(timestamp) = self.timestamp { |
| 822 | use super::ParseError as PE; |
| 823 | use super::ParseErrorKind::{Impossible, OutOfRange}; |
| 824 | |
| 825 | // if date and time is problematic already, there is no point proceeding. |
| 826 | // we at least try to give a correct error though. |
| 827 | match (date, time) { |
| 828 | (Err(PE(OutOfRange)), _) | (_, Err(PE(OutOfRange))) => return Err(OUT_OF_RANGE), |
| 829 | (Err(PE(Impossible)), _) | (_, Err(PE(Impossible))) => return Err(IMPOSSIBLE), |
| 830 | (_, _) => {} // one of them is insufficient |
| 831 | } |
| 832 | |
| 833 | // reconstruct date and time fields from timestamp |
| 834 | let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; |
| 835 | let mut datetime = DateTime::from_timestamp(ts, 0).ok_or(OUT_OF_RANGE)?.naive_utc(); |
| 836 | |
| 837 | // fill year, ordinal, hour, minute and second fields from timestamp. |
| 838 | // if existing fields are consistent, this will allow the full date/time reconstruction. |
| 839 | let mut parsed = self.clone(); |
| 840 | if parsed.second == Some(60) { |
| 841 | // `datetime.second()` cannot be 60, so this is the only case for a leap second. |
| 842 | match datetime.second() { |
| 843 | // it's okay, just do not try to overwrite the existing field. |
| 844 | 59 => {} |
| 845 | // `datetime` is known to be off by one second. |
| 846 | 0 => { |
| 847 | datetime -= TimeDelta::try_seconds(1).unwrap(); |
| 848 | } |
| 849 | // otherwise it is impossible. |
| 850 | _ => return Err(IMPOSSIBLE), |
| 851 | } |
| 852 | // ...and we have the correct candidates for other fields. |
| 853 | } else { |
| 854 | parsed.set_second(i64::from(datetime.second()))?; |
| 855 | } |
| 856 | parsed.set_year(i64::from(datetime.year()))?; |
| 857 | parsed.set_ordinal(i64::from(datetime.ordinal()))?; // more efficient than ymd |
| 858 | parsed.set_hour(i64::from(datetime.hour()))?; |
| 859 | parsed.set_minute(i64::from(datetime.minute()))?; |
| 860 | |
| 861 | // validate other fields (e.g. week) and return |
| 862 | let date = parsed.to_naive_date()?; |
| 863 | let time = parsed.to_naive_time()?; |
| 864 | Ok(date.and_time(time)) |
| 865 | } else { |
| 866 | // reproduce the previous error(s) |
| 867 | date?; |
| 868 | time?; |
| 869 | unreachable!() |
| 870 | } |
| 871 | } |
| 872 | |
| 873 | /// Returns a parsed fixed time zone offset out of given fields. |
| 874 | /// |
| 875 | /// # Errors |
| 876 | /// |
| 877 | /// This method returns: |
| 878 | /// - `OUT_OF_RANGE` if the offset is out of range for a `FixedOffset`. |
| 879 | /// - `NOT_ENOUGH` if the offset field is not set. |
| 880 | pub fn to_fixed_offset(&self) -> ParseResult<FixedOffset> { |
| 881 | FixedOffset::east_opt(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE) |
| 882 | } |
| 883 | |
| 884 | /// Returns a parsed timezone-aware date and time out of given fields. |
| 885 | /// |
| 886 | /// This method is able to determine the combined date and time from date, time and offset |
| 887 | /// fields, and/or from a single timestamp field. It checks all fields are consistent with each |
| 888 | /// other. |
| 889 | /// |
| 890 | /// # Errors |
| 891 | /// |
| 892 | /// This method returns: |
| 893 | /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of |
| 894 | /// the other fields. |
| 895 | /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime |
| 896 | /// including offset from UTC. |
| 897 | /// - `OUT_OF_RANGE` |
| 898 | /// - if any of the fields of `Parsed` are set to a value beyond their acceptable |
| 899 | /// range. |
| 900 | /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. |
| 901 | /// - if the date does not exist. |
| 902 | pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> { |
| 903 | // If there is no explicit offset, consider a timestamp value as indication of a UTC value. |
| 904 | let offset = match (self.offset, self.timestamp) { |
| 905 | (Some(off), _) => off, |
| 906 | (None, Some(_)) => 0, // UNIX timestamp may assume 0 offset |
| 907 | (None, None) => return Err(NOT_ENOUGH), |
| 908 | }; |
| 909 | let datetime = self.to_naive_datetime_with_offset(offset)?; |
| 910 | let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; |
| 911 | |
| 912 | match offset.from_local_datetime(&datetime) { |
| 913 | MappedLocalTime::None => Err(IMPOSSIBLE), |
| 914 | MappedLocalTime::Single(t) => Ok(t), |
| 915 | MappedLocalTime::Ambiguous(..) => Err(NOT_ENOUGH), |
| 916 | } |
| 917 | } |
| 918 | |
| 919 | /// Returns a parsed timezone-aware date and time out of given fields, |
| 920 | /// with an additional [`TimeZone`] used to interpret and validate the local date. |
| 921 | /// |
| 922 | /// This method is able to determine the combined date and time from date and time, and/or from |
| 923 | /// a single timestamp field. It checks all fields are consistent with each other. |
| 924 | /// |
| 925 | /// If the parsed fields include an UTC offset, it also has to be consistent with the offset in |
| 926 | /// the provided `tz` time zone for that datetime. |
| 927 | /// |
| 928 | /// # Errors |
| 929 | /// |
| 930 | /// This method returns: |
| 931 | /// - `IMPOSSIBLE` |
| 932 | /// - if any of the date fields conflict, if a timestamp conflicts with any of the other |
| 933 | /// fields, or if the offset field is set but differs from the offset at that time in the |
| 934 | /// `tz` time zone. |
| 935 | /// - if the local datetime does not exists in the provided time zone (because it falls in a |
| 936 | /// transition due to for example DST). |
| 937 | /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime, or if |
| 938 | /// the local time in the provided time zone is ambiguous (because it falls in a transition |
| 939 | /// due to for example DST) while there is no offset field or timestamp field set. |
| 940 | /// - `OUT_OF_RANGE` |
| 941 | /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. |
| 942 | /// - if any of the fields of `Parsed` are set to a value beyond their acceptable range. |
| 943 | /// - if the date does not exist. |
| 944 | pub fn to_datetime_with_timezone<Tz: TimeZone>(&self, tz: &Tz) -> ParseResult<DateTime<Tz>> { |
| 945 | // if we have `timestamp` specified, guess an offset from that. |
| 946 | let mut guessed_offset = 0; |
| 947 | if let Some(timestamp) = self.timestamp { |
| 948 | // make a naive `DateTime` from given timestamp and (if any) nanosecond. |
| 949 | // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. |
| 950 | let nanosecond = self.nanosecond.unwrap_or(0); |
| 951 | let dt = |
| 952 | DateTime::from_timestamp(timestamp, nanosecond).ok_or(OUT_OF_RANGE)?.naive_utc(); |
| 953 | guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); |
| 954 | } |
| 955 | |
| 956 | // checks if the given `DateTime` has a consistent `Offset` with given `self.offset`. |
| 957 | let check_offset = |dt: &DateTime<Tz>| { |
| 958 | if let Some(offset) = self.offset { |
| 959 | dt.offset().fix().local_minus_utc() == offset |
| 960 | } else { |
| 961 | true |
| 962 | } |
| 963 | }; |
| 964 | |
| 965 | // `guessed_offset` should be correct when `self.timestamp` is given. |
| 966 | // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. |
| 967 | let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; |
| 968 | match tz.from_local_datetime(&datetime) { |
| 969 | MappedLocalTime::None => Err(IMPOSSIBLE), |
| 970 | MappedLocalTime::Single(t) => { |
| 971 | if check_offset(&t) { |
| 972 | Ok(t) |
| 973 | } else { |
| 974 | Err(IMPOSSIBLE) |
| 975 | } |
| 976 | } |
| 977 | MappedLocalTime::Ambiguous(min, max) => { |
| 978 | // try to disambiguate two possible local dates by offset. |
| 979 | match (check_offset(&min), check_offset(&max)) { |
| 980 | (false, false) => Err(IMPOSSIBLE), |
| 981 | (false, true) => Ok(max), |
| 982 | (true, false) => Ok(min), |
| 983 | (true, true) => Err(NOT_ENOUGH), |
| 984 | } |
| 985 | } |
| 986 | } |
| 987 | } |
| 988 | |
| 989 | /// Get the `year` field if set. |
| 990 | /// |
| 991 | /// See also [`set_year()`](Parsed::set_year). |
| 992 | #[inline ] |
| 993 | pub fn year(&self) -> Option<i32> { |
| 994 | self.year |
| 995 | } |
| 996 | |
| 997 | /// Get the `year_div_100` field if set. |
| 998 | /// |
| 999 | /// See also [`set_year_div_100()`](Parsed::set_year_div_100). |
| 1000 | #[inline ] |
| 1001 | pub fn year_div_100(&self) -> Option<i32> { |
| 1002 | self.year_div_100 |
| 1003 | } |
| 1004 | |
| 1005 | /// Get the `year_mod_100` field if set. |
| 1006 | /// |
| 1007 | /// See also [`set_year_mod_100()`](Parsed::set_year_mod_100). |
| 1008 | #[inline ] |
| 1009 | pub fn year_mod_100(&self) -> Option<i32> { |
| 1010 | self.year_mod_100 |
| 1011 | } |
| 1012 | |
| 1013 | /// Get the `isoyear` field that is part of an [ISO 8601 week date] if set. |
| 1014 | /// |
| 1015 | /// See also [`set_isoyear()`](Parsed::set_isoyear). |
| 1016 | /// |
| 1017 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 1018 | #[inline ] |
| 1019 | pub fn isoyear(&self) -> Option<i32> { |
| 1020 | self.isoyear |
| 1021 | } |
| 1022 | |
| 1023 | /// Get the `isoyear_div_100` field that is part of an [ISO 8601 week date] if set. |
| 1024 | /// |
| 1025 | /// See also [`set_isoyear_div_100()`](Parsed::set_isoyear_div_100). |
| 1026 | /// |
| 1027 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 1028 | #[inline ] |
| 1029 | pub fn isoyear_div_100(&self) -> Option<i32> { |
| 1030 | self.isoyear_div_100 |
| 1031 | } |
| 1032 | |
| 1033 | /// Get the `isoyear_mod_100` field that is part of an [ISO 8601 week date] if set. |
| 1034 | /// |
| 1035 | /// See also [`set_isoyear_mod_100()`](Parsed::set_isoyear_mod_100). |
| 1036 | /// |
| 1037 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 1038 | #[inline ] |
| 1039 | pub fn isoyear_mod_100(&self) -> Option<i32> { |
| 1040 | self.isoyear_mod_100 |
| 1041 | } |
| 1042 | |
| 1043 | /// Get the `quarter` field if set. |
| 1044 | /// |
| 1045 | /// See also [`set_quarter()`](Parsed::set_quarter). |
| 1046 | #[inline ] |
| 1047 | pub fn quarter(&self) -> Option<u32> { |
| 1048 | self.quarter |
| 1049 | } |
| 1050 | |
| 1051 | /// Get the `month` field if set. |
| 1052 | /// |
| 1053 | /// See also [`set_month()`](Parsed::set_month). |
| 1054 | #[inline ] |
| 1055 | pub fn month(&self) -> Option<u32> { |
| 1056 | self.month |
| 1057 | } |
| 1058 | |
| 1059 | /// Get the `week_from_sun` field if set. |
| 1060 | /// |
| 1061 | /// See also [`set_week_from_sun()`](Parsed::set_week_from_sun). |
| 1062 | #[inline ] |
| 1063 | pub fn week_from_sun(&self) -> Option<u32> { |
| 1064 | self.week_from_sun |
| 1065 | } |
| 1066 | |
| 1067 | /// Get the `week_from_mon` field if set. |
| 1068 | /// |
| 1069 | /// See also [`set_week_from_mon()`](Parsed::set_week_from_mon). |
| 1070 | #[inline ] |
| 1071 | pub fn week_from_mon(&self) -> Option<u32> { |
| 1072 | self.week_from_mon |
| 1073 | } |
| 1074 | |
| 1075 | /// Get the `isoweek` field that is part of an [ISO 8601 week date] if set. |
| 1076 | /// |
| 1077 | /// See also [`set_isoweek()`](Parsed::set_isoweek). |
| 1078 | /// |
| 1079 | /// [ISO 8601 week date]: crate::NaiveDate#week-date |
| 1080 | #[inline ] |
| 1081 | pub fn isoweek(&self) -> Option<u32> { |
| 1082 | self.isoweek |
| 1083 | } |
| 1084 | |
| 1085 | /// Get the `weekday` field if set. |
| 1086 | /// |
| 1087 | /// See also [`set_weekday()`](Parsed::set_weekday). |
| 1088 | #[inline ] |
| 1089 | pub fn weekday(&self) -> Option<Weekday> { |
| 1090 | self.weekday |
| 1091 | } |
| 1092 | |
| 1093 | /// Get the `ordinal` (day of the year) field if set. |
| 1094 | /// |
| 1095 | /// See also [`set_ordinal()`](Parsed::set_ordinal). |
| 1096 | #[inline ] |
| 1097 | pub fn ordinal(&self) -> Option<u32> { |
| 1098 | self.ordinal |
| 1099 | } |
| 1100 | |
| 1101 | /// Get the `day` of the month field if set. |
| 1102 | /// |
| 1103 | /// See also [`set_day()`](Parsed::set_day). |
| 1104 | #[inline ] |
| 1105 | pub fn day(&self) -> Option<u32> { |
| 1106 | self.day |
| 1107 | } |
| 1108 | |
| 1109 | /// Get the `hour_div_12` field (am/pm) if set. |
| 1110 | /// |
| 1111 | /// 0 indicates AM and 1 indicates PM. |
| 1112 | /// |
| 1113 | /// See also [`set_ampm()`](Parsed::set_ampm) and [`set_hour()`](Parsed::set_hour). |
| 1114 | #[inline ] |
| 1115 | pub fn hour_div_12(&self) -> Option<u32> { |
| 1116 | self.hour_div_12 |
| 1117 | } |
| 1118 | |
| 1119 | /// Get the `hour_mod_12` field if set. |
| 1120 | /// |
| 1121 | /// See also [`set_hour12()`](Parsed::set_hour12) and [`set_hour()`](Parsed::set_hour). |
| 1122 | pub fn hour_mod_12(&self) -> Option<u32> { |
| 1123 | self.hour_mod_12 |
| 1124 | } |
| 1125 | |
| 1126 | /// Get the `minute` field if set. |
| 1127 | /// |
| 1128 | /// See also [`set_minute()`](Parsed::set_minute). |
| 1129 | #[inline ] |
| 1130 | pub fn minute(&self) -> Option<u32> { |
| 1131 | self.minute |
| 1132 | } |
| 1133 | |
| 1134 | /// Get the `second` field if set. |
| 1135 | /// |
| 1136 | /// See also [`set_second()`](Parsed::set_second). |
| 1137 | #[inline ] |
| 1138 | pub fn second(&self) -> Option<u32> { |
| 1139 | self.second |
| 1140 | } |
| 1141 | |
| 1142 | /// Get the `nanosecond` field if set. |
| 1143 | /// |
| 1144 | /// See also [`set_nanosecond()`](Parsed::set_nanosecond). |
| 1145 | #[inline ] |
| 1146 | pub fn nanosecond(&self) -> Option<u32> { |
| 1147 | self.nanosecond |
| 1148 | } |
| 1149 | |
| 1150 | /// Get the `timestamp` field if set. |
| 1151 | /// |
| 1152 | /// See also [`set_timestamp()`](Parsed::set_timestamp). |
| 1153 | #[inline ] |
| 1154 | pub fn timestamp(&self) -> Option<i64> { |
| 1155 | self.timestamp |
| 1156 | } |
| 1157 | |
| 1158 | /// Get the `offset` field if set. |
| 1159 | /// |
| 1160 | /// See also [`set_offset()`](Parsed::set_offset). |
| 1161 | #[inline ] |
| 1162 | pub fn offset(&self) -> Option<i32> { |
| 1163 | self.offset |
| 1164 | } |
| 1165 | } |
| 1166 | |
| 1167 | /// Create a `NaiveDate` when given a year, week, weekday, and the definition at which day of the |
| 1168 | /// week a week starts. |
| 1169 | /// |
| 1170 | /// Returns `IMPOSSIBLE` if `week` is `0` or `53` and the `weekday` falls outside the year. |
| 1171 | fn resolve_week_date( |
| 1172 | year: i32, |
| 1173 | week: u32, |
| 1174 | weekday: Weekday, |
| 1175 | week_start_day: Weekday, |
| 1176 | ) -> ParseResult<NaiveDate> { |
| 1177 | if week > 53 { |
| 1178 | return Err(OUT_OF_RANGE); |
| 1179 | } |
| 1180 | |
| 1181 | let first_day_of_year: NaiveDate = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; |
| 1182 | // Ordinal of the day at which week 1 starts. |
| 1183 | let first_week_start: i32 = 1 + week_start_day.days_since(first_day_of_year.weekday()) as i32; |
| 1184 | // Number of the `weekday`, which is 0 for the first day of the week. |
| 1185 | let weekday: i32 = weekday.days_since(week_start_day) as i32; |
| 1186 | let ordinal: i32 = first_week_start + (week as i32 - 1) * 7 + weekday; |
| 1187 | if ordinal <= 0 { |
| 1188 | return Err(IMPOSSIBLE); |
| 1189 | } |
| 1190 | first_day_of_year.with_ordinal(ordinal as u32).ok_or(IMPOSSIBLE) |
| 1191 | } |
| 1192 | |
| 1193 | #[cfg (test)] |
| 1194 | mod tests { |
| 1195 | use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; |
| 1196 | use super::Parsed; |
| 1197 | use crate::Datelike; |
| 1198 | use crate::Weekday::*; |
| 1199 | use crate::naive::{NaiveDate, NaiveTime}; |
| 1200 | use crate::offset::{FixedOffset, TimeZone, Utc}; |
| 1201 | |
| 1202 | #[test ] |
| 1203 | fn test_parsed_set_fields() { |
| 1204 | // year*, isoyear* |
| 1205 | let mut p = Parsed::new(); |
| 1206 | assert_eq!(p.set_year(1987), Ok(())); |
| 1207 | assert_eq!(p.set_year(1986), Err(IMPOSSIBLE)); |
| 1208 | assert_eq!(p.set_year(1988), Err(IMPOSSIBLE)); |
| 1209 | assert_eq!(p.set_year(1987), Ok(())); |
| 1210 | assert_eq!(p.set_year_div_100(20), Ok(())); // independent to `year` |
| 1211 | assert_eq!(p.set_year_div_100(21), Err(IMPOSSIBLE)); |
| 1212 | assert_eq!(p.set_year_div_100(19), Err(IMPOSSIBLE)); |
| 1213 | assert_eq!(p.set_year_mod_100(37), Ok(())); // ditto |
| 1214 | assert_eq!(p.set_year_mod_100(38), Err(IMPOSSIBLE)); |
| 1215 | assert_eq!(p.set_year_mod_100(36), Err(IMPOSSIBLE)); |
| 1216 | |
| 1217 | let mut p = Parsed::new(); |
| 1218 | assert_eq!(p.set_year(0), Ok(())); |
| 1219 | assert_eq!(p.set_year_div_100(0), Ok(())); |
| 1220 | assert_eq!(p.set_year_mod_100(0), Ok(())); |
| 1221 | |
| 1222 | let mut p = Parsed::new(); |
| 1223 | assert_eq!(p.set_year_div_100(-1), Err(OUT_OF_RANGE)); |
| 1224 | assert_eq!(p.set_year_mod_100(-1), Err(OUT_OF_RANGE)); |
| 1225 | assert_eq!(p.set_year(-1), Ok(())); |
| 1226 | assert_eq!(p.set_year(-2), Err(IMPOSSIBLE)); |
| 1227 | assert_eq!(p.set_year(0), Err(IMPOSSIBLE)); |
| 1228 | |
| 1229 | let mut p = Parsed::new(); |
| 1230 | assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); |
| 1231 | assert_eq!(p.set_year_div_100(8), Ok(())); |
| 1232 | assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); |
| 1233 | |
| 1234 | // month, week*, isoweek, ordinal, day, minute, second, nanosecond, offset |
| 1235 | let mut p = Parsed::new(); |
| 1236 | assert_eq!(p.set_month(7), Ok(())); |
| 1237 | assert_eq!(p.set_month(1), Err(IMPOSSIBLE)); |
| 1238 | assert_eq!(p.set_month(6), Err(IMPOSSIBLE)); |
| 1239 | assert_eq!(p.set_month(8), Err(IMPOSSIBLE)); |
| 1240 | assert_eq!(p.set_month(12), Err(IMPOSSIBLE)); |
| 1241 | |
| 1242 | let mut p = Parsed::new(); |
| 1243 | assert_eq!(p.set_month(8), Ok(())); |
| 1244 | assert_eq!(p.set_month(0x1_0000_0008), Err(OUT_OF_RANGE)); |
| 1245 | |
| 1246 | // hour |
| 1247 | let mut p = Parsed::new(); |
| 1248 | assert_eq!(p.set_hour(12), Ok(())); |
| 1249 | assert_eq!(p.set_hour(11), Err(IMPOSSIBLE)); |
| 1250 | assert_eq!(p.set_hour(13), Err(IMPOSSIBLE)); |
| 1251 | assert_eq!(p.set_hour(12), Ok(())); |
| 1252 | assert_eq!(p.set_ampm(false), Err(IMPOSSIBLE)); |
| 1253 | assert_eq!(p.set_ampm(true), Ok(())); |
| 1254 | assert_eq!(p.set_hour12(12), Ok(())); |
| 1255 | assert_eq!(p.set_hour12(0), Err(OUT_OF_RANGE)); // requires canonical representation |
| 1256 | assert_eq!(p.set_hour12(1), Err(IMPOSSIBLE)); |
| 1257 | assert_eq!(p.set_hour12(11), Err(IMPOSSIBLE)); |
| 1258 | |
| 1259 | let mut p = Parsed::new(); |
| 1260 | assert_eq!(p.set_ampm(true), Ok(())); |
| 1261 | assert_eq!(p.set_hour12(7), Ok(())); |
| 1262 | assert_eq!(p.set_hour(7), Err(IMPOSSIBLE)); |
| 1263 | assert_eq!(p.set_hour(18), Err(IMPOSSIBLE)); |
| 1264 | assert_eq!(p.set_hour(19), Ok(())); |
| 1265 | |
| 1266 | // timestamp |
| 1267 | let mut p = Parsed::new(); |
| 1268 | assert_eq!(p.set_timestamp(1_234_567_890), Ok(())); |
| 1269 | assert_eq!(p.set_timestamp(1_234_567_889), Err(IMPOSSIBLE)); |
| 1270 | assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); |
| 1271 | } |
| 1272 | |
| 1273 | #[test ] |
| 1274 | fn test_parsed_set_range() { |
| 1275 | assert_eq!(Parsed::new().set_year(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); |
| 1276 | assert!(Parsed::new().set_year(i32::MIN as i64).is_ok()); |
| 1277 | assert!(Parsed::new().set_year(i32::MAX as i64).is_ok()); |
| 1278 | assert_eq!(Parsed::new().set_year(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); |
| 1279 | |
| 1280 | assert_eq!(Parsed::new().set_year_div_100(-1), Err(OUT_OF_RANGE)); |
| 1281 | assert!(Parsed::new().set_year_div_100(0).is_ok()); |
| 1282 | assert!(Parsed::new().set_year_div_100(i32::MAX as i64).is_ok()); |
| 1283 | assert_eq!(Parsed::new().set_year_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); |
| 1284 | |
| 1285 | assert_eq!(Parsed::new().set_year_mod_100(-1), Err(OUT_OF_RANGE)); |
| 1286 | assert!(Parsed::new().set_year_mod_100(0).is_ok()); |
| 1287 | assert!(Parsed::new().set_year_mod_100(99).is_ok()); |
| 1288 | assert_eq!(Parsed::new().set_year_mod_100(100), Err(OUT_OF_RANGE)); |
| 1289 | |
| 1290 | assert_eq!(Parsed::new().set_isoyear(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); |
| 1291 | assert!(Parsed::new().set_isoyear(i32::MIN as i64).is_ok()); |
| 1292 | assert!(Parsed::new().set_isoyear(i32::MAX as i64).is_ok()); |
| 1293 | assert_eq!(Parsed::new().set_isoyear(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); |
| 1294 | |
| 1295 | assert_eq!(Parsed::new().set_isoyear_div_100(-1), Err(OUT_OF_RANGE)); |
| 1296 | assert!(Parsed::new().set_isoyear_div_100(0).is_ok()); |
| 1297 | assert!(Parsed::new().set_isoyear_div_100(99).is_ok()); |
| 1298 | assert_eq!(Parsed::new().set_isoyear_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); |
| 1299 | |
| 1300 | assert_eq!(Parsed::new().set_isoyear_mod_100(-1), Err(OUT_OF_RANGE)); |
| 1301 | assert!(Parsed::new().set_isoyear_mod_100(0).is_ok()); |
| 1302 | assert!(Parsed::new().set_isoyear_mod_100(99).is_ok()); |
| 1303 | assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE)); |
| 1304 | |
| 1305 | assert_eq!(Parsed::new().set_quarter(0), Err(OUT_OF_RANGE)); |
| 1306 | assert!(Parsed::new().set_quarter(1).is_ok()); |
| 1307 | assert!(Parsed::new().set_quarter(4).is_ok()); |
| 1308 | assert_eq!(Parsed::new().set_quarter(5), Err(OUT_OF_RANGE)); |
| 1309 | |
| 1310 | assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE)); |
| 1311 | assert!(Parsed::new().set_month(1).is_ok()); |
| 1312 | assert!(Parsed::new().set_month(12).is_ok()); |
| 1313 | assert_eq!(Parsed::new().set_month(13), Err(OUT_OF_RANGE)); |
| 1314 | |
| 1315 | assert_eq!(Parsed::new().set_week_from_sun(-1), Err(OUT_OF_RANGE)); |
| 1316 | assert!(Parsed::new().set_week_from_sun(0).is_ok()); |
| 1317 | assert!(Parsed::new().set_week_from_sun(53).is_ok()); |
| 1318 | assert_eq!(Parsed::new().set_week_from_sun(54), Err(OUT_OF_RANGE)); |
| 1319 | |
| 1320 | assert_eq!(Parsed::new().set_week_from_mon(-1), Err(OUT_OF_RANGE)); |
| 1321 | assert!(Parsed::new().set_week_from_mon(0).is_ok()); |
| 1322 | assert!(Parsed::new().set_week_from_mon(53).is_ok()); |
| 1323 | assert_eq!(Parsed::new().set_week_from_mon(54), Err(OUT_OF_RANGE)); |
| 1324 | |
| 1325 | assert_eq!(Parsed::new().set_isoweek(0), Err(OUT_OF_RANGE)); |
| 1326 | assert!(Parsed::new().set_isoweek(1).is_ok()); |
| 1327 | assert!(Parsed::new().set_isoweek(53).is_ok()); |
| 1328 | assert_eq!(Parsed::new().set_isoweek(54), Err(OUT_OF_RANGE)); |
| 1329 | |
| 1330 | assert_eq!(Parsed::new().set_ordinal(0), Err(OUT_OF_RANGE)); |
| 1331 | assert!(Parsed::new().set_ordinal(1).is_ok()); |
| 1332 | assert!(Parsed::new().set_ordinal(366).is_ok()); |
| 1333 | assert_eq!(Parsed::new().set_ordinal(367), Err(OUT_OF_RANGE)); |
| 1334 | |
| 1335 | assert_eq!(Parsed::new().set_day(0), Err(OUT_OF_RANGE)); |
| 1336 | assert!(Parsed::new().set_day(1).is_ok()); |
| 1337 | assert!(Parsed::new().set_day(31).is_ok()); |
| 1338 | assert_eq!(Parsed::new().set_day(32), Err(OUT_OF_RANGE)); |
| 1339 | |
| 1340 | assert_eq!(Parsed::new().set_hour12(0), Err(OUT_OF_RANGE)); |
| 1341 | assert!(Parsed::new().set_hour12(1).is_ok()); |
| 1342 | assert!(Parsed::new().set_hour12(12).is_ok()); |
| 1343 | assert_eq!(Parsed::new().set_hour12(13), Err(OUT_OF_RANGE)); |
| 1344 | |
| 1345 | assert_eq!(Parsed::new().set_hour(-1), Err(OUT_OF_RANGE)); |
| 1346 | assert!(Parsed::new().set_hour(0).is_ok()); |
| 1347 | assert!(Parsed::new().set_hour(23).is_ok()); |
| 1348 | assert_eq!(Parsed::new().set_hour(24), Err(OUT_OF_RANGE)); |
| 1349 | |
| 1350 | assert_eq!(Parsed::new().set_minute(-1), Err(OUT_OF_RANGE)); |
| 1351 | assert!(Parsed::new().set_minute(0).is_ok()); |
| 1352 | assert!(Parsed::new().set_minute(59).is_ok()); |
| 1353 | assert_eq!(Parsed::new().set_minute(60), Err(OUT_OF_RANGE)); |
| 1354 | |
| 1355 | assert_eq!(Parsed::new().set_second(-1), Err(OUT_OF_RANGE)); |
| 1356 | assert!(Parsed::new().set_second(0).is_ok()); |
| 1357 | assert!(Parsed::new().set_second(60).is_ok()); |
| 1358 | assert_eq!(Parsed::new().set_second(61), Err(OUT_OF_RANGE)); |
| 1359 | |
| 1360 | assert_eq!(Parsed::new().set_nanosecond(-1), Err(OUT_OF_RANGE)); |
| 1361 | assert!(Parsed::new().set_nanosecond(0).is_ok()); |
| 1362 | assert!(Parsed::new().set_nanosecond(999_999_999).is_ok()); |
| 1363 | assert_eq!(Parsed::new().set_nanosecond(1_000_000_000), Err(OUT_OF_RANGE)); |
| 1364 | |
| 1365 | assert!(Parsed::new().set_timestamp(i64::MIN).is_ok()); |
| 1366 | assert!(Parsed::new().set_timestamp(i64::MAX).is_ok()); |
| 1367 | |
| 1368 | assert_eq!(Parsed::new().set_offset(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); |
| 1369 | assert!(Parsed::new().set_offset(i32::MIN as i64).is_ok()); |
| 1370 | assert!(Parsed::new().set_offset(i32::MAX as i64).is_ok()); |
| 1371 | assert_eq!(Parsed::new().set_offset(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); |
| 1372 | } |
| 1373 | |
| 1374 | #[test ] |
| 1375 | fn test_parsed_to_naive_date() { |
| 1376 | macro_rules! parse { |
| 1377 | ($($k:ident: $v:expr),*) => ( |
| 1378 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_date() |
| 1379 | ) |
| 1380 | } |
| 1381 | |
| 1382 | let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); |
| 1383 | |
| 1384 | // ymd: omission of fields |
| 1385 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
| 1386 | assert_eq!(parse!(year: 1984), Err(NOT_ENOUGH)); |
| 1387 | assert_eq!(parse!(year: 1984, month: 1), Err(NOT_ENOUGH)); |
| 1388 | assert_eq!(parse!(year: 1984, month: 1, day: 2), ymd(1984, 1, 2)); |
| 1389 | assert_eq!(parse!(year: 1984, day: 2), Err(NOT_ENOUGH)); |
| 1390 | assert_eq!(parse!(year_div_100: 19), Err(NOT_ENOUGH)); |
| 1391 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84), Err(NOT_ENOUGH)); |
| 1392 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1), Err(NOT_ENOUGH)); |
| 1393 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1, day: 2), ymd(1984, 1, 2)); |
| 1394 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, day: 2), Err(NOT_ENOUGH)); |
| 1395 | assert_eq!(parse!(year_div_100: 19, month: 1, day: 2), Err(NOT_ENOUGH)); |
| 1396 | assert_eq!(parse!(year_mod_100: 70, month: 1, day: 2), ymd(1970, 1, 2)); |
| 1397 | assert_eq!(parse!(year_mod_100: 69, month: 1, day: 2), ymd(2069, 1, 2)); |
| 1398 | |
| 1399 | // ymd: out-of-range conditions |
| 1400 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 2, day: 29), ymd(1984, 2, 29)); |
| 1401 | assert_eq!( |
| 1402 | parse!(year_div_100: 19, year_mod_100: 83, month: 2, day: 29), |
| 1403 | Err(OUT_OF_RANGE) |
| 1404 | ); |
| 1405 | assert_eq!( |
| 1406 | parse!(year_div_100: 19, year_mod_100: 83, month: 13, day: 1), |
| 1407 | Err(OUT_OF_RANGE) |
| 1408 | ); |
| 1409 | assert_eq!( |
| 1410 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 31), |
| 1411 | ymd(1983, 12, 31) |
| 1412 | ); |
| 1413 | assert_eq!( |
| 1414 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 32), |
| 1415 | Err(OUT_OF_RANGE) |
| 1416 | ); |
| 1417 | assert_eq!( |
| 1418 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 0), |
| 1419 | Err(OUT_OF_RANGE) |
| 1420 | ); |
| 1421 | assert_eq!( |
| 1422 | parse!(year_div_100: 19, year_mod_100: 100, month: 1, day: 1), |
| 1423 | Err(OUT_OF_RANGE) |
| 1424 | ); |
| 1425 | assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); |
| 1426 | assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); |
| 1427 | assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(IMPOSSIBLE)); |
| 1428 | let max_year = NaiveDate::MAX.year(); |
| 1429 | assert_eq!( |
| 1430 | parse!(year_div_100: max_year / 100, |
| 1431 | year_mod_100: max_year % 100, month: 1, day: 1), |
| 1432 | ymd(max_year, 1, 1) |
| 1433 | ); |
| 1434 | assert_eq!( |
| 1435 | parse!(year_div_100: (max_year + 1) / 100, |
| 1436 | year_mod_100: (max_year + 1) % 100, month: 1, day: 1), |
| 1437 | Err(OUT_OF_RANGE) |
| 1438 | ); |
| 1439 | |
| 1440 | // ymd: conflicting inputs |
| 1441 | assert_eq!(parse!(year: 1984, year_div_100: 19, month: 1, day: 1), ymd(1984, 1, 1)); |
| 1442 | assert_eq!(parse!(year: 1984, year_div_100: 20, month: 1, day: 1), Err(IMPOSSIBLE)); |
| 1443 | assert_eq!(parse!(year: 1984, year_mod_100: 84, month: 1, day: 1), ymd(1984, 1, 1)); |
| 1444 | assert_eq!(parse!(year: 1984, year_mod_100: 83, month: 1, day: 1), Err(IMPOSSIBLE)); |
| 1445 | assert_eq!( |
| 1446 | parse!(year: 1984, year_div_100: 19, year_mod_100: 84, month: 1, day: 1), |
| 1447 | ymd(1984, 1, 1) |
| 1448 | ); |
| 1449 | assert_eq!( |
| 1450 | parse!(year: 1984, year_div_100: 18, year_mod_100: 94, month: 1, day: 1), |
| 1451 | Err(IMPOSSIBLE) |
| 1452 | ); |
| 1453 | assert_eq!( |
| 1454 | parse!(year: 1984, year_div_100: 18, year_mod_100: 184, month: 1, day: 1), |
| 1455 | Err(OUT_OF_RANGE) |
| 1456 | ); |
| 1457 | assert_eq!( |
| 1458 | parse!(year: -1, year_div_100: 0, year_mod_100: -1, month: 1, day: 1), |
| 1459 | Err(OUT_OF_RANGE) |
| 1460 | ); |
| 1461 | assert_eq!( |
| 1462 | parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), |
| 1463 | Err(IMPOSSIBLE) |
| 1464 | ); |
| 1465 | assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE)); |
| 1466 | assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE)); |
| 1467 | |
| 1468 | // quarters |
| 1469 | assert_eq!(parse!(year: 2000, quarter: 1), Err(NOT_ENOUGH)); |
| 1470 | assert_eq!(parse!(year: 2000, quarter: 1, month: 1, day: 1), ymd(2000, 1, 1)); |
| 1471 | assert_eq!(parse!(year: 2000, quarter: 2, month: 4, day: 1), ymd(2000, 4, 1)); |
| 1472 | assert_eq!(parse!(year: 2000, quarter: 3, month: 7, day: 1), ymd(2000, 7, 1)); |
| 1473 | assert_eq!(parse!(year: 2000, quarter: 4, month: 10, day: 1), ymd(2000, 10, 1)); |
| 1474 | |
| 1475 | // quarter: conflicting inputs |
| 1476 | assert_eq!(parse!(year: 2000, quarter: 2, month: 3, day: 31), Err(IMPOSSIBLE)); |
| 1477 | assert_eq!(parse!(year: 2000, quarter: 4, month: 3, day: 31), Err(IMPOSSIBLE)); |
| 1478 | |
| 1479 | // weekdates |
| 1480 | assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); |
| 1481 | assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); |
| 1482 | assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); |
| 1483 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(IMPOSSIBLE)); |
| 1484 | assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(IMPOSSIBLE)); |
| 1485 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); |
| 1486 | assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); |
| 1487 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); |
| 1488 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sun), ymd(2000, 1, 2)); |
| 1489 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Mon), ymd(2000, 1, 3)); |
| 1490 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Mon), ymd(2000, 1, 3)); |
| 1491 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sat), ymd(2000, 1, 8)); |
| 1492 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sat), ymd(2000, 1, 8)); |
| 1493 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sun), ymd(2000, 1, 9)); |
| 1494 | assert_eq!(parse!(year: 2000, week_from_sun: 2, weekday: Sun), ymd(2000, 1, 9)); |
| 1495 | assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); |
| 1496 | assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); |
| 1497 | assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); |
| 1498 | assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(IMPOSSIBLE)); |
| 1499 | assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); |
| 1500 | assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(IMPOSSIBLE)); |
| 1501 | assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); |
| 1502 | |
| 1503 | // weekdates: conflicting inputs |
| 1504 | assert_eq!( |
| 1505 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sat), |
| 1506 | ymd(2000, 1, 8) |
| 1507 | ); |
| 1508 | assert_eq!( |
| 1509 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 2, weekday: Sun), |
| 1510 | ymd(2000, 1, 9) |
| 1511 | ); |
| 1512 | assert_eq!( |
| 1513 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sun), |
| 1514 | Err(IMPOSSIBLE) |
| 1515 | ); |
| 1516 | assert_eq!( |
| 1517 | parse!(year: 2000, week_from_mon: 2, week_from_sun: 2, weekday: Sun), |
| 1518 | Err(IMPOSSIBLE) |
| 1519 | ); |
| 1520 | |
| 1521 | // ISO weekdates |
| 1522 | assert_eq!(parse!(isoyear: 2004, isoweek: 53), Err(NOT_ENOUGH)); |
| 1523 | assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Fri), ymd(2004, 12, 31)); |
| 1524 | assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Sat), ymd(2005, 1, 1)); |
| 1525 | assert_eq!(parse!(isoyear: 2004, isoweek: 0xffffffff, weekday: Sat), Err(OUT_OF_RANGE)); |
| 1526 | assert_eq!(parse!(isoyear: 2005, isoweek: 0, weekday: Thu), Err(OUT_OF_RANGE)); |
| 1527 | assert_eq!(parse!(isoyear: 2005, isoweek: 5, weekday: Thu), ymd(2005, 2, 3)); |
| 1528 | assert_eq!(parse!(isoyear: 2005, weekday: Thu), Err(NOT_ENOUGH)); |
| 1529 | |
| 1530 | // year and ordinal |
| 1531 | assert_eq!(parse!(ordinal: 123), Err(NOT_ENOUGH)); |
| 1532 | assert_eq!(parse!(year: 2000, ordinal: 0), Err(OUT_OF_RANGE)); |
| 1533 | assert_eq!(parse!(year: 2000, ordinal: 1), ymd(2000, 1, 1)); |
| 1534 | assert_eq!(parse!(year: 2000, ordinal: 60), ymd(2000, 2, 29)); |
| 1535 | assert_eq!(parse!(year: 2000, ordinal: 61), ymd(2000, 3, 1)); |
| 1536 | assert_eq!(parse!(year: 2000, ordinal: 366), ymd(2000, 12, 31)); |
| 1537 | assert_eq!(parse!(year: 2000, ordinal: 367), Err(OUT_OF_RANGE)); |
| 1538 | assert_eq!(parse!(year: 2000, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); |
| 1539 | assert_eq!(parse!(year: 2100, ordinal: 0), Err(OUT_OF_RANGE)); |
| 1540 | assert_eq!(parse!(year: 2100, ordinal: 1), ymd(2100, 1, 1)); |
| 1541 | assert_eq!(parse!(year: 2100, ordinal: 59), ymd(2100, 2, 28)); |
| 1542 | assert_eq!(parse!(year: 2100, ordinal: 60), ymd(2100, 3, 1)); |
| 1543 | assert_eq!(parse!(year: 2100, ordinal: 365), ymd(2100, 12, 31)); |
| 1544 | assert_eq!(parse!(year: 2100, ordinal: 366), Err(OUT_OF_RANGE)); |
| 1545 | assert_eq!(parse!(year: 2100, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); |
| 1546 | |
| 1547 | // more complex cases |
| 1548 | assert_eq!( |
| 1549 | parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoweek: 1, |
| 1550 | week_from_sun: 52, week_from_mon: 52, weekday: Wed), |
| 1551 | ymd(2014, 12, 31) |
| 1552 | ); |
| 1553 | assert_eq!( |
| 1554 | parse!(year: 2014, month: 12, ordinal: 365, isoyear: 2015, isoweek: 1, |
| 1555 | week_from_sun: 52, week_from_mon: 52), |
| 1556 | ymd(2014, 12, 31) |
| 1557 | ); |
| 1558 | assert_eq!( |
| 1559 | parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2014, isoweek: 53, |
| 1560 | week_from_sun: 52, week_from_mon: 52, weekday: Wed), |
| 1561 | Err(IMPOSSIBLE) |
| 1562 | ); // no ISO week date 2014-W53-3 |
| 1563 | assert_eq!( |
| 1564 | parse!(year: 2012, isoyear: 2015, isoweek: 1, |
| 1565 | week_from_sun: 52, week_from_mon: 52), |
| 1566 | Err(NOT_ENOUGH) |
| 1567 | ); // ambiguous (2014-12-29, 2014-12-30, 2014-12-31) |
| 1568 | assert_eq!(parse!(year_div_100: 20, isoyear_mod_100: 15, ordinal: 366), Err(NOT_ENOUGH)); |
| 1569 | // technically unique (2014-12-31) but Chrono gives up |
| 1570 | } |
| 1571 | |
| 1572 | #[test ] |
| 1573 | fn test_parsed_to_naive_time() { |
| 1574 | macro_rules! parse { |
| 1575 | ($($k:ident: $v:expr),*) => ( |
| 1576 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_time() |
| 1577 | ) |
| 1578 | } |
| 1579 | |
| 1580 | let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); |
| 1581 | let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); |
| 1582 | |
| 1583 | // omission of fields |
| 1584 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
| 1585 | assert_eq!(parse!(hour_div_12: 0), Err(NOT_ENOUGH)); |
| 1586 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1), Err(NOT_ENOUGH)); |
| 1587 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23), hms(1, 23, 0)); |
| 1588 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45), hms(1, 23, 45)); |
| 1589 | assert_eq!( |
| 1590 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45, |
| 1591 | nanosecond: 678_901_234), |
| 1592 | hmsn(1, 23, 45, 678_901_234) |
| 1593 | ); |
| 1594 | assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6), hms(23, 45, 6)); |
| 1595 | assert_eq!(parse!(hour_mod_12: 1, minute: 23), Err(NOT_ENOUGH)); |
| 1596 | assert_eq!( |
| 1597 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, nanosecond: 456_789_012), |
| 1598 | Err(NOT_ENOUGH) |
| 1599 | ); |
| 1600 | |
| 1601 | // out-of-range conditions |
| 1602 | assert_eq!(parse!(hour_div_12: 2, hour_mod_12: 0, minute: 0), Err(OUT_OF_RANGE)); |
| 1603 | assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 12, minute: 0), Err(OUT_OF_RANGE)); |
| 1604 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 60), Err(OUT_OF_RANGE)); |
| 1605 | assert_eq!( |
| 1606 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 61), |
| 1607 | Err(OUT_OF_RANGE) |
| 1608 | ); |
| 1609 | assert_eq!( |
| 1610 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 34, |
| 1611 | nanosecond: 1_000_000_000), |
| 1612 | Err(OUT_OF_RANGE) |
| 1613 | ); |
| 1614 | |
| 1615 | // leap seconds |
| 1616 | assert_eq!( |
| 1617 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60), |
| 1618 | hmsn(1, 23, 59, 1_000_000_000) |
| 1619 | ); |
| 1620 | assert_eq!( |
| 1621 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60, |
| 1622 | nanosecond: 999_999_999), |
| 1623 | hmsn(1, 23, 59, 1_999_999_999) |
| 1624 | ); |
| 1625 | } |
| 1626 | |
| 1627 | #[test ] |
| 1628 | fn test_parsed_to_naive_datetime_with_offset() { |
| 1629 | macro_rules! parse { |
| 1630 | (offset = $offset:expr; $($k:ident: $v:expr),*) => ( |
| 1631 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_datetime_with_offset($offset) |
| 1632 | ); |
| 1633 | ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) |
| 1634 | } |
| 1635 | |
| 1636 | let ymdhms = |y, m, d, h, n, s| { |
| 1637 | Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) |
| 1638 | }; |
| 1639 | let ymdhmsn = |y, m, d, h, n, s, nano| { |
| 1640 | Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) |
| 1641 | }; |
| 1642 | |
| 1643 | // omission of fields |
| 1644 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
| 1645 | assert_eq!( |
| 1646 | parse!(year: 2015, month: 1, day: 30, |
| 1647 | hour_div_12: 1, hour_mod_12: 2, minute: 38), |
| 1648 | ymdhms(2015, 1, 30, 14, 38, 0) |
| 1649 | ); |
| 1650 | assert_eq!( |
| 1651 | parse!(year: 1997, month: 1, day: 30, |
| 1652 | hour_div_12: 1, hour_mod_12: 2, minute: 38, second: 5), |
| 1653 | ymdhms(1997, 1, 30, 14, 38, 5) |
| 1654 | ); |
| 1655 | assert_eq!( |
| 1656 | parse!(year: 2012, ordinal: 34, hour_div_12: 0, hour_mod_12: 5, |
| 1657 | minute: 6, second: 7, nanosecond: 890_123_456), |
| 1658 | ymdhmsn(2012, 2, 3, 5, 6, 7, 890_123_456) |
| 1659 | ); |
| 1660 | assert_eq!(parse!(timestamp: 0), ymdhms(1970, 1, 1, 0, 0, 0)); |
| 1661 | assert_eq!(parse!(timestamp: 1, nanosecond: 0), ymdhms(1970, 1, 1, 0, 0, 1)); |
| 1662 | assert_eq!(parse!(timestamp: 1, nanosecond: 1), ymdhmsn(1970, 1, 1, 0, 0, 1, 1)); |
| 1663 | assert_eq!(parse!(timestamp: 1_420_000_000), ymdhms(2014, 12, 31, 4, 26, 40)); |
| 1664 | assert_eq!(parse!(timestamp: -0x1_0000_0000), ymdhms(1833, 11, 24, 17, 31, 44)); |
| 1665 | |
| 1666 | // full fields |
| 1667 | assert_eq!( |
| 1668 | parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
| 1669 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
| 1670 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
| 1671 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
| 1672 | nanosecond: 12_345_678, timestamp: 1_420_000_000), |
| 1673 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) |
| 1674 | ); |
| 1675 | assert_eq!( |
| 1676 | parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
| 1677 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
| 1678 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
| 1679 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
| 1680 | nanosecond: 12_345_678, timestamp: 1_419_999_999), |
| 1681 | Err(IMPOSSIBLE) |
| 1682 | ); |
| 1683 | assert_eq!( |
| 1684 | parse!(offset = 32400; |
| 1685 | year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
| 1686 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
| 1687 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
| 1688 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
| 1689 | nanosecond: 12_345_678, timestamp: 1_419_967_600), |
| 1690 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) |
| 1691 | ); |
| 1692 | |
| 1693 | // more timestamps |
| 1694 | let max_days_from_year_1970 = |
| 1695 | NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
| 1696 | let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) |
| 1697 | .unwrap() |
| 1698 | .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
| 1699 | let min_days_from_year_1970 = |
| 1700 | NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
| 1701 | assert_eq!( |
| 1702 | parse!(timestamp: min_days_from_year_1970.num_seconds()), |
| 1703 | ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) |
| 1704 | ); |
| 1705 | assert_eq!( |
| 1706 | parse!(timestamp: year_0_from_year_1970.num_seconds()), |
| 1707 | ymdhms(0, 1, 1, 0, 0, 0) |
| 1708 | ); |
| 1709 | assert_eq!( |
| 1710 | parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), |
| 1711 | ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) |
| 1712 | ); |
| 1713 | |
| 1714 | // leap seconds #1: partial fields |
| 1715 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_798), Err(IMPOSSIBLE)); |
| 1716 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_799), ymdhms(2012, 6, 30, 23, 59, 59)); |
| 1717 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); |
| 1718 | assert_eq!( |
| 1719 | parse!(second: 60, timestamp: 1_341_100_799), |
| 1720 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
| 1721 | ); |
| 1722 | assert_eq!( |
| 1723 | parse!(second: 60, timestamp: 1_341_100_800), |
| 1724 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
| 1725 | ); |
| 1726 | assert_eq!(parse!(second: 0, timestamp: 1_341_100_800), ymdhms(2012, 7, 1, 0, 0, 0)); |
| 1727 | assert_eq!(parse!(second: 1, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); |
| 1728 | assert_eq!(parse!(second: 60, timestamp: 1_341_100_801), Err(IMPOSSIBLE)); |
| 1729 | |
| 1730 | // leap seconds #2: full fields |
| 1731 | // we need to have separate tests for them since it uses another control flow. |
| 1732 | assert_eq!( |
| 1733 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1734 | minute: 59, second: 59, timestamp: 1_341_100_798), |
| 1735 | Err(IMPOSSIBLE) |
| 1736 | ); |
| 1737 | assert_eq!( |
| 1738 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1739 | minute: 59, second: 59, timestamp: 1_341_100_799), |
| 1740 | ymdhms(2012, 6, 30, 23, 59, 59) |
| 1741 | ); |
| 1742 | assert_eq!( |
| 1743 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1744 | minute: 59, second: 59, timestamp: 1_341_100_800), |
| 1745 | Err(IMPOSSIBLE) |
| 1746 | ); |
| 1747 | assert_eq!( |
| 1748 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1749 | minute: 59, second: 60, timestamp: 1_341_100_799), |
| 1750 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
| 1751 | ); |
| 1752 | assert_eq!( |
| 1753 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1754 | minute: 59, second: 60, timestamp: 1_341_100_800), |
| 1755 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
| 1756 | ); |
| 1757 | assert_eq!( |
| 1758 | parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, |
| 1759 | minute: 0, second: 0, timestamp: 1_341_100_800), |
| 1760 | ymdhms(2012, 7, 1, 0, 0, 0) |
| 1761 | ); |
| 1762 | assert_eq!( |
| 1763 | parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, |
| 1764 | minute: 0, second: 1, timestamp: 1_341_100_800), |
| 1765 | Err(IMPOSSIBLE) |
| 1766 | ); |
| 1767 | assert_eq!( |
| 1768 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
| 1769 | minute: 59, second: 60, timestamp: 1_341_100_801), |
| 1770 | Err(IMPOSSIBLE) |
| 1771 | ); |
| 1772 | |
| 1773 | // error codes |
| 1774 | assert_eq!( |
| 1775 | parse!(year: 2015, month: 1, day: 20, weekday: Tue, |
| 1776 | hour_div_12: 2, hour_mod_12: 1, minute: 35, second: 20), |
| 1777 | Err(OUT_OF_RANGE) |
| 1778 | ); // `hour_div_12` is out of range |
| 1779 | } |
| 1780 | |
| 1781 | #[test ] |
| 1782 | fn test_parsed_to_datetime() { |
| 1783 | macro_rules! parse { |
| 1784 | ($($k:ident: $v:expr),*) => ( |
| 1785 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime() |
| 1786 | ) |
| 1787 | } |
| 1788 | |
| 1789 | let ymdhmsn = |y, m, d, h, n, s, nano, off| { |
| 1790 | Ok(FixedOffset::east_opt(off) |
| 1791 | .unwrap() |
| 1792 | .from_local_datetime( |
| 1793 | &NaiveDate::from_ymd_opt(y, m, d) |
| 1794 | .unwrap() |
| 1795 | .and_hms_nano_opt(h, n, s, nano) |
| 1796 | .unwrap(), |
| 1797 | ) |
| 1798 | .unwrap()) |
| 1799 | }; |
| 1800 | |
| 1801 | assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); |
| 1802 | assert_eq!( |
| 1803 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
| 1804 | minute: 26, second: 40, nanosecond: 12_345_678), |
| 1805 | Err(NOT_ENOUGH) |
| 1806 | ); |
| 1807 | assert_eq!( |
| 1808 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
| 1809 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
| 1810 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678, 0) |
| 1811 | ); |
| 1812 | assert_eq!( |
| 1813 | parse!(year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
| 1814 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
| 1815 | ymdhmsn(2014, 12, 31, 13, 26, 40, 12_345_678, 32400) |
| 1816 | ); |
| 1817 | assert_eq!( |
| 1818 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 1, |
| 1819 | minute: 42, second: 4, nanosecond: 12_345_678, offset: -9876), |
| 1820 | ymdhmsn(2014, 12, 31, 1, 42, 4, 12_345_678, -9876) |
| 1821 | ); |
| 1822 | assert_eq!( |
| 1823 | parse!(year: 2015, ordinal: 1, hour_div_12: 0, hour_mod_12: 4, |
| 1824 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 86_400), |
| 1825 | Err(OUT_OF_RANGE) |
| 1826 | ); // `FixedOffset` does not support such huge offset |
| 1827 | } |
| 1828 | |
| 1829 | #[test ] |
| 1830 | fn test_parsed_to_datetime_with_timezone() { |
| 1831 | macro_rules! parse { |
| 1832 | ($tz:expr; $($k:ident: $v:expr),*) => ( |
| 1833 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime_with_timezone(&$tz) |
| 1834 | ) |
| 1835 | } |
| 1836 | |
| 1837 | // single result from ymdhms |
| 1838 | assert_eq!( |
| 1839 | parse!(Utc; |
| 1840 | year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
| 1841 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
| 1842 | Ok(Utc |
| 1843 | .from_local_datetime( |
| 1844 | &NaiveDate::from_ymd_opt(2014, 12, 31) |
| 1845 | .unwrap() |
| 1846 | .and_hms_nano_opt(4, 26, 40, 12_345_678) |
| 1847 | .unwrap() |
| 1848 | ) |
| 1849 | .unwrap()) |
| 1850 | ); |
| 1851 | assert_eq!( |
| 1852 | parse!(Utc; |
| 1853 | year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
| 1854 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
| 1855 | Err(IMPOSSIBLE) |
| 1856 | ); |
| 1857 | assert_eq!( |
| 1858 | parse!(FixedOffset::east_opt(32400).unwrap(); |
| 1859 | year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
| 1860 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
| 1861 | Err(IMPOSSIBLE) |
| 1862 | ); |
| 1863 | assert_eq!( |
| 1864 | parse!(FixedOffset::east_opt(32400).unwrap(); |
| 1865 | year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
| 1866 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
| 1867 | Ok(FixedOffset::east_opt(32400) |
| 1868 | .unwrap() |
| 1869 | .from_local_datetime( |
| 1870 | &NaiveDate::from_ymd_opt(2014, 12, 31) |
| 1871 | .unwrap() |
| 1872 | .and_hms_nano_opt(13, 26, 40, 12_345_678) |
| 1873 | .unwrap() |
| 1874 | ) |
| 1875 | .unwrap()) |
| 1876 | ); |
| 1877 | |
| 1878 | // single result from timestamp |
| 1879 | assert_eq!( |
| 1880 | parse!(Utc; timestamp: 1_420_000_000, offset: 0), |
| 1881 | Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) |
| 1882 | ); |
| 1883 | assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); |
| 1884 | assert_eq!( |
| 1885 | parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), |
| 1886 | Err(IMPOSSIBLE) |
| 1887 | ); |
| 1888 | assert_eq!( |
| 1889 | parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), |
| 1890 | Ok(FixedOffset::east_opt(32400) |
| 1891 | .unwrap() |
| 1892 | .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) |
| 1893 | .unwrap()) |
| 1894 | ); |
| 1895 | |
| 1896 | // TODO test with a variable time zone (for None and Ambiguous cases) |
| 1897 | } |
| 1898 | |
| 1899 | #[test ] |
| 1900 | fn issue_551() { |
| 1901 | use crate::Weekday; |
| 1902 | let mut parsed = Parsed::new(); |
| 1903 | |
| 1904 | parsed.year = Some(2002); |
| 1905 | parsed.week_from_mon = Some(22); |
| 1906 | parsed.weekday = Some(Weekday::Mon); |
| 1907 | assert_eq!(NaiveDate::from_ymd_opt(2002, 6, 3).unwrap(), parsed.to_naive_date().unwrap()); |
| 1908 | |
| 1909 | parsed.year = Some(2001); |
| 1910 | assert_eq!(NaiveDate::from_ymd_opt(2001, 5, 28).unwrap(), parsed.to_naive_date().unwrap()); |
| 1911 | } |
| 1912 | } |
| 1913 | |