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::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; |
8 | use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; |
9 | use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; |
10 | use crate::oldtime::Duration as OldDuration; |
11 | use crate::DateTime; |
12 | use crate::Weekday; |
13 | use crate::{Datelike, Timelike}; |
14 | |
15 | /// Parsed parts of date and time. There are two classes of methods: |
16 | /// |
17 | /// - `set_*` methods try to set given field(s) while checking for the consistency. |
18 | /// It may or may not check for the range constraint immediately (for efficiency reasons). |
19 | /// |
20 | /// - `to_*` methods try to make a concrete date and time value out of set fields. |
21 | /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. |
22 | #[allow (clippy::manual_non_exhaustive)] |
23 | #[derive (Clone, PartialEq, Eq, Debug, Default, Hash)] |
24 | pub struct Parsed { |
25 | /// Year. |
26 | /// |
27 | /// This can be negative unlike [`year_div_100`](#structfield.year_div_100) |
28 | /// and [`year_mod_100`](#structfield.year_mod_100) fields. |
29 | pub year: Option<i32>, |
30 | |
31 | /// Year divided by 100. Implies that the year is >= 1 BCE when set. |
32 | /// |
33 | /// Due to the common usage, if this field is missing but |
34 | /// [`year_mod_100`](#structfield.year_mod_100) is present, |
35 | /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise. |
36 | pub year_div_100: Option<i32>, |
37 | |
38 | /// Year modulo 100. Implies that the year is >= 1 BCE when set. |
39 | pub year_mod_100: Option<i32>, |
40 | |
41 | /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). |
42 | /// |
43 | /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and |
44 | /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. |
45 | pub isoyear: Option<i32>, |
46 | |
47 | /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100. |
48 | /// Implies that the year is >= 1 BCE when set. |
49 | /// |
50 | /// Due to the common usage, if this field is missing but |
51 | /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present, |
52 | /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise. |
53 | pub isoyear_div_100: Option<i32>, |
54 | |
55 | /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100. |
56 | /// Implies that the year is >= 1 BCE when set. |
57 | pub isoyear_mod_100: Option<i32>, |
58 | |
59 | /// Month (1--12). |
60 | pub month: Option<u32>, |
61 | |
62 | /// Week number, where the week 1 starts at the first Sunday of January |
63 | /// (0--53, 1--53 or 1--52 depending on the year). |
64 | pub week_from_sun: Option<u32>, |
65 | |
66 | /// Week number, where the week 1 starts at the first Monday of January |
67 | /// (0--53, 1--53 or 1--52 depending on the year). |
68 | pub week_from_mon: Option<u32>, |
69 | |
70 | /// [ISO week number](../naive/struct.NaiveDate.html#week-date) |
71 | /// (1--52 or 1--53 depending on the year). |
72 | pub isoweek: Option<u32>, |
73 | |
74 | /// Day of the week. |
75 | pub weekday: Option<Weekday>, |
76 | |
77 | /// Day of the year (1--365 or 1--366 depending on the year). |
78 | pub ordinal: Option<u32>, |
79 | |
80 | /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). |
81 | pub day: Option<u32>, |
82 | |
83 | /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. |
84 | pub hour_div_12: Option<u32>, |
85 | |
86 | /// Hour number modulo 12 (0--11). |
87 | pub hour_mod_12: Option<u32>, |
88 | |
89 | /// Minute number (0--59). |
90 | pub minute: Option<u32>, |
91 | |
92 | /// Second number (0--60, accounting for leap seconds). |
93 | pub second: Option<u32>, |
94 | |
95 | /// The number of nanoseconds since the whole second (0--999,999,999). |
96 | pub nanosecond: Option<u32>, |
97 | |
98 | /// The number of non-leap seconds since the midnight UTC on January 1, 1970. |
99 | /// |
100 | /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second). |
101 | pub timestamp: Option<i64>, |
102 | |
103 | /// Offset from the local time to UTC, in seconds. |
104 | pub offset: Option<i32>, |
105 | |
106 | /// A dummy field to make this type not fully destructible (required for API stability). |
107 | // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. |
108 | _dummy: (), |
109 | } |
110 | |
111 | /// Checks if `old` is either empty or has the same value as `new` (i.e. "consistent"), |
112 | /// and if it is empty, set `old` to `new` as well. |
113 | #[inline ] |
114 | fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<()> { |
115 | if let Some(ref old: &T) = *old { |
116 | if *old == new { |
117 | Ok(()) |
118 | } else { |
119 | Err(IMPOSSIBLE) |
120 | } |
121 | } else { |
122 | *old = Some(new); |
123 | Ok(()) |
124 | } |
125 | } |
126 | |
127 | impl Parsed { |
128 | /// Returns the initial value of parsed parts. |
129 | #[must_use ] |
130 | pub fn new() -> Parsed { |
131 | Parsed::default() |
132 | } |
133 | |
134 | /// Tries to set the [`year`](#structfield.year) field from given value. |
135 | #[inline ] |
136 | pub fn set_year(&mut self, value: i64) -> ParseResult<()> { |
137 | set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
138 | } |
139 | |
140 | /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value. |
141 | #[inline ] |
142 | pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { |
143 | if value < 0 { |
144 | return Err(OUT_OF_RANGE); |
145 | } |
146 | set_if_consistent(&mut self.year_div_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
147 | } |
148 | |
149 | /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value. |
150 | #[inline ] |
151 | pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { |
152 | if value < 0 { |
153 | return Err(OUT_OF_RANGE); |
154 | } |
155 | set_if_consistent(&mut self.year_mod_100, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
156 | } |
157 | |
158 | /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value. |
159 | #[inline ] |
160 | pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { |
161 | set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
162 | } |
163 | |
164 | /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. |
165 | #[inline ] |
166 | pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { |
167 | if value < 0 { |
168 | return Err(OUT_OF_RANGE); |
169 | } |
170 | set_if_consistent( |
171 | &mut self.isoyear_div_100, |
172 | i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, |
173 | ) |
174 | } |
175 | |
176 | /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value. |
177 | #[inline ] |
178 | pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { |
179 | if value < 0 { |
180 | return Err(OUT_OF_RANGE); |
181 | } |
182 | set_if_consistent( |
183 | &mut self.isoyear_mod_100, |
184 | i32::try_from(value).map_err(|_| OUT_OF_RANGE)?, |
185 | ) |
186 | } |
187 | |
188 | /// Tries to set the [`month`](#structfield.month) field from given value. |
189 | #[inline ] |
190 | pub fn set_month(&mut self, value: i64) -> ParseResult<()> { |
191 | set_if_consistent(&mut self.month, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
192 | } |
193 | |
194 | /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value. |
195 | #[inline ] |
196 | pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { |
197 | set_if_consistent(&mut self.week_from_sun, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
198 | } |
199 | |
200 | /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value. |
201 | #[inline ] |
202 | pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { |
203 | set_if_consistent(&mut self.week_from_mon, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
204 | } |
205 | |
206 | /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value. |
207 | #[inline ] |
208 | pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { |
209 | set_if_consistent(&mut self.isoweek, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
210 | } |
211 | |
212 | /// Tries to set the [`weekday`](#structfield.weekday) field from given value. |
213 | #[inline ] |
214 | pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { |
215 | set_if_consistent(&mut self.weekday, value) |
216 | } |
217 | |
218 | /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. |
219 | #[inline ] |
220 | pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { |
221 | set_if_consistent(&mut self.ordinal, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
222 | } |
223 | |
224 | /// Tries to set the [`day`](#structfield.day) field from given value. |
225 | #[inline ] |
226 | pub fn set_day(&mut self, value: i64) -> ParseResult<()> { |
227 | set_if_consistent(&mut self.day, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
228 | } |
229 | |
230 | /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value. |
231 | /// (`false` for AM, `true` for PM) |
232 | #[inline ] |
233 | pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { |
234 | set_if_consistent(&mut self.hour_div_12, u32::from(value)) |
235 | } |
236 | |
237 | /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from |
238 | /// given hour number in 12-hour clocks. |
239 | #[inline ] |
240 | pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { |
241 | if !(1..=12).contains(&value) { |
242 | return Err(OUT_OF_RANGE); |
243 | } |
244 | set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) |
245 | } |
246 | |
247 | /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and |
248 | /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. |
249 | #[inline ] |
250 | pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { |
251 | let v = u32::try_from(value).map_err(|_| OUT_OF_RANGE)?; |
252 | set_if_consistent(&mut self.hour_div_12, v / 12)?; |
253 | set_if_consistent(&mut self.hour_mod_12, v % 12)?; |
254 | Ok(()) |
255 | } |
256 | |
257 | /// Tries to set the [`minute`](#structfield.minute) field from given value. |
258 | #[inline ] |
259 | pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { |
260 | set_if_consistent(&mut self.minute, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
261 | } |
262 | |
263 | /// Tries to set the [`second`](#structfield.second) field from given value. |
264 | #[inline ] |
265 | pub fn set_second(&mut self, value: i64) -> ParseResult<()> { |
266 | set_if_consistent(&mut self.second, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
267 | } |
268 | |
269 | /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. |
270 | #[inline ] |
271 | pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { |
272 | set_if_consistent(&mut self.nanosecond, u32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
273 | } |
274 | |
275 | /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. |
276 | #[inline ] |
277 | pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { |
278 | set_if_consistent(&mut self.timestamp, value) |
279 | } |
280 | |
281 | /// Tries to set the [`offset`](#structfield.offset) field from given value. |
282 | #[inline ] |
283 | pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { |
284 | set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) |
285 | } |
286 | |
287 | /// Returns a parsed naive date out of given fields. |
288 | /// |
289 | /// This method is able to determine the date from given subset of fields: |
290 | /// |
291 | /// - Year, month, day. |
292 | /// - Year, day of the year (ordinal). |
293 | /// - Year, week number counted from Sunday or Monday, day of the week. |
294 | /// - ISO week date. |
295 | /// |
296 | /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, |
297 | /// the two-digit year is used to guess the century number then. |
298 | pub fn to_naive_date(&self) -> ParseResult<NaiveDate> { |
299 | fn resolve_year( |
300 | y: Option<i32>, |
301 | q: Option<i32>, |
302 | r: Option<i32>, |
303 | ) -> ParseResult<Option<i32>> { |
304 | match (y, q, r) { |
305 | // if there is no further information, simply return the given full year. |
306 | // this is a common case, so let's avoid division here. |
307 | (y, None, None) => Ok(y), |
308 | |
309 | // if there is a full year *and* also quotient and/or modulo, |
310 | // check if present quotient and/or modulo is consistent to the full year. |
311 | // since the presence of those fields means a positive full year, |
312 | // we should filter a negative full year first. |
313 | (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { |
314 | if y < 0 { |
315 | return Err(OUT_OF_RANGE); |
316 | } |
317 | let q_ = y / 100; |
318 | let r_ = y % 100; |
319 | if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { |
320 | Ok(Some(y)) |
321 | } else { |
322 | Err(IMPOSSIBLE) |
323 | } |
324 | } |
325 | |
326 | // the full year is missing but we have quotient and modulo. |
327 | // reconstruct the full year. make sure that the result is always positive. |
328 | (None, Some(q), Some(r @ 0..=99)) => { |
329 | if q < 0 { |
330 | return Err(OUT_OF_RANGE); |
331 | } |
332 | let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); |
333 | Ok(Some(y.ok_or(OUT_OF_RANGE)?)) |
334 | } |
335 | |
336 | // we only have modulo. try to interpret a modulo as a conventional two-digit year. |
337 | // note: we are affected by Rust issue #18060. avoid multiple range patterns. |
338 | (None, None, Some(r @ 0..=99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), |
339 | |
340 | // otherwise it is an out-of-bound or insufficient condition. |
341 | (None, Some(_), None) => Err(NOT_ENOUGH), |
342 | (_, _, Some(_)) => Err(OUT_OF_RANGE), |
343 | } |
344 | } |
345 | |
346 | let given_year = resolve_year(self.year, self.year_div_100, self.year_mod_100)?; |
347 | let given_isoyear = resolve_year(self.isoyear, self.isoyear_div_100, self.isoyear_mod_100)?; |
348 | |
349 | // verify the normal year-month-day date. |
350 | let verify_ymd = |date: NaiveDate| { |
351 | let year = date.year(); |
352 | let (year_div_100, year_mod_100) = if year >= 0 { |
353 | (Some(year / 100), Some(year % 100)) |
354 | } else { |
355 | (None, None) // they should be empty to be consistent |
356 | }; |
357 | let month = date.month(); |
358 | let day = date.day(); |
359 | self.year.unwrap_or(year) == year |
360 | && self.year_div_100.or(year_div_100) == year_div_100 |
361 | && self.year_mod_100.or(year_mod_100) == year_mod_100 |
362 | && self.month.unwrap_or(month) == month |
363 | && self.day.unwrap_or(day) == day |
364 | }; |
365 | |
366 | // verify the ISO week date. |
367 | let verify_isoweekdate = |date: NaiveDate| { |
368 | let week = date.iso_week(); |
369 | let isoyear = week.year(); |
370 | let isoweek = week.week(); |
371 | let weekday = date.weekday(); |
372 | let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { |
373 | (Some(isoyear / 100), Some(isoyear % 100)) |
374 | } else { |
375 | (None, None) // they should be empty to be consistent |
376 | }; |
377 | self.isoyear.unwrap_or(isoyear) == isoyear |
378 | && self.isoyear_div_100.or(isoyear_div_100) == isoyear_div_100 |
379 | && self.isoyear_mod_100.or(isoyear_mod_100) == isoyear_mod_100 |
380 | && self.isoweek.unwrap_or(isoweek) == isoweek |
381 | && self.weekday.unwrap_or(weekday) == weekday |
382 | }; |
383 | |
384 | // verify the ordinal and other (non-ISO) week dates. |
385 | let verify_ordinal = |date: NaiveDate| { |
386 | let ordinal = date.ordinal(); |
387 | let week_from_sun = date.weeks_from(Weekday::Sun); |
388 | let week_from_mon = date.weeks_from(Weekday::Mon); |
389 | self.ordinal.unwrap_or(ordinal) == ordinal |
390 | && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun |
391 | && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon |
392 | }; |
393 | |
394 | // test several possibilities. |
395 | // tries to construct a full `NaiveDate` as much as possible, then verifies that |
396 | // it is consistent with other given fields. |
397 | let (verified, parsed_date) = match (given_year, given_isoyear, self) { |
398 | (Some(year), _, &Parsed { month: Some(month), day: Some(day), .. }) => { |
399 | // year, month, day |
400 | let date = NaiveDate::from_ymd_opt(year, month, day).ok_or(OUT_OF_RANGE)?; |
401 | (verify_isoweekdate(date) && verify_ordinal(date), date) |
402 | } |
403 | |
404 | (Some(year), _, &Parsed { ordinal: Some(ordinal), .. }) => { |
405 | // year, day of the year |
406 | let date = NaiveDate::from_yo_opt(year, ordinal).ok_or(OUT_OF_RANGE)?; |
407 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
408 | } |
409 | |
410 | ( |
411 | Some(year), |
412 | _, |
413 | &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. }, |
414 | ) => { |
415 | // year, week (starting at 1st Sunday), day of the week |
416 | let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; |
417 | let firstweek = match newyear.weekday() { |
418 | Weekday::Sun => 0, |
419 | Weekday::Mon => 6, |
420 | Weekday::Tue => 5, |
421 | Weekday::Wed => 4, |
422 | Weekday::Thu => 3, |
423 | Weekday::Fri => 2, |
424 | Weekday::Sat => 1, |
425 | }; |
426 | |
427 | // `firstweek+1`-th day of January is the beginning of the week 1. |
428 | if week_from_sun > 53 { |
429 | return Err(OUT_OF_RANGE); |
430 | } // can it overflow? |
431 | let ndays = firstweek |
432 | + (week_from_sun as i32 - 1) * 7 |
433 | + weekday.num_days_from_sunday() as i32; |
434 | let date = newyear |
435 | .checked_add_signed(OldDuration::days(i64::from(ndays))) |
436 | .ok_or(OUT_OF_RANGE)?; |
437 | if date.year() != year { |
438 | return Err(OUT_OF_RANGE); |
439 | } // early exit for correct error |
440 | |
441 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
442 | } |
443 | |
444 | ( |
445 | Some(year), |
446 | _, |
447 | &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. }, |
448 | ) => { |
449 | // year, week (starting at 1st Monday), day of the week |
450 | let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; |
451 | let firstweek = match newyear.weekday() { |
452 | Weekday::Sun => 1, |
453 | Weekday::Mon => 0, |
454 | Weekday::Tue => 6, |
455 | Weekday::Wed => 5, |
456 | Weekday::Thu => 4, |
457 | Weekday::Fri => 3, |
458 | Weekday::Sat => 2, |
459 | }; |
460 | |
461 | // `firstweek+1`-th day of January is the beginning of the week 1. |
462 | if week_from_mon > 53 { |
463 | return Err(OUT_OF_RANGE); |
464 | } // can it overflow? |
465 | let ndays = firstweek |
466 | + (week_from_mon as i32 - 1) * 7 |
467 | + weekday.num_days_from_monday() as i32; |
468 | let date = newyear |
469 | .checked_add_signed(OldDuration::days(i64::from(ndays))) |
470 | .ok_or(OUT_OF_RANGE)?; |
471 | if date.year() != year { |
472 | return Err(OUT_OF_RANGE); |
473 | } // early exit for correct error |
474 | |
475 | (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) |
476 | } |
477 | |
478 | (_, Some(isoyear), &Parsed { isoweek: Some(isoweek), weekday: Some(weekday), .. }) => { |
479 | // ISO year, week, day of the week |
480 | let date = NaiveDate::from_isoywd_opt(isoyear, isoweek, weekday); |
481 | let date = date.ok_or(OUT_OF_RANGE)?; |
482 | (verify_ymd(date) && verify_ordinal(date), date) |
483 | } |
484 | |
485 | (_, _, _) => return Err(NOT_ENOUGH), |
486 | }; |
487 | |
488 | if verified { |
489 | Ok(parsed_date) |
490 | } else { |
491 | Err(IMPOSSIBLE) |
492 | } |
493 | } |
494 | |
495 | /// Returns a parsed naive time out of given fields. |
496 | /// |
497 | /// This method is able to determine the time from given subset of fields: |
498 | /// |
499 | /// - Hour, minute. (second and nanosecond assumed to be 0) |
500 | /// - Hour, minute, second. (nanosecond assumed to be 0) |
501 | /// - Hour, minute, second, nanosecond. |
502 | /// |
503 | /// It is able to handle leap seconds when given second is 60. |
504 | pub fn to_naive_time(&self) -> ParseResult<NaiveTime> { |
505 | let hour_div_12 = match self.hour_div_12 { |
506 | Some(v @ 0..=1) => v, |
507 | Some(_) => return Err(OUT_OF_RANGE), |
508 | None => return Err(NOT_ENOUGH), |
509 | }; |
510 | let hour_mod_12 = match self.hour_mod_12 { |
511 | Some(v @ 0..=11) => v, |
512 | Some(_) => return Err(OUT_OF_RANGE), |
513 | None => return Err(NOT_ENOUGH), |
514 | }; |
515 | let hour = hour_div_12 * 12 + hour_mod_12; |
516 | |
517 | let minute = match self.minute { |
518 | Some(v @ 0..=59) => v, |
519 | Some(_) => return Err(OUT_OF_RANGE), |
520 | None => return Err(NOT_ENOUGH), |
521 | }; |
522 | |
523 | // we allow omitting seconds or nanoseconds, but they should be in the range. |
524 | let (second, mut nano) = match self.second.unwrap_or(0) { |
525 | v @ 0..=59 => (v, 0), |
526 | 60 => (59, 1_000_000_000), |
527 | _ => return Err(OUT_OF_RANGE), |
528 | }; |
529 | nano += match self.nanosecond { |
530 | Some(v @ 0..=999_999_999) if self.second.is_some() => v, |
531 | Some(0..=999_999_999) => return Err(NOT_ENOUGH), // second is missing |
532 | Some(_) => return Err(OUT_OF_RANGE), |
533 | None => 0, |
534 | }; |
535 | |
536 | NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) |
537 | } |
538 | |
539 | /// Returns a parsed naive date and time out of given fields, |
540 | /// except for the [`offset`](#structfield.offset) field (assumed to have a given value). |
541 | /// This is required for parsing a local time or other known-timezone inputs. |
542 | /// |
543 | /// This method is able to determine the combined date and time |
544 | /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field. |
545 | /// Either way those fields have to be consistent to each other. |
546 | pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult<NaiveDateTime> { |
547 | let date = self.to_naive_date(); |
548 | let time = self.to_naive_time(); |
549 | if let (Ok(date), Ok(time)) = (date, time) { |
550 | let datetime = date.and_time(time); |
551 | |
552 | // verify the timestamp field if any |
553 | // the following is safe, `timestamp` is very limited in range |
554 | let timestamp = datetime.timestamp() - i64::from(offset); |
555 | if let Some(given_timestamp) = self.timestamp { |
556 | // if `datetime` represents a leap second, it might be off by one second. |
557 | if given_timestamp != timestamp |
558 | && !(datetime.nanosecond() >= 1_000_000_000 && given_timestamp == timestamp + 1) |
559 | { |
560 | return Err(IMPOSSIBLE); |
561 | } |
562 | } |
563 | |
564 | Ok(datetime) |
565 | } else if let Some(timestamp) = self.timestamp { |
566 | use super::ParseError as PE; |
567 | use super::ParseErrorKind::{Impossible, OutOfRange}; |
568 | |
569 | // if date and time is problematic already, there is no point proceeding. |
570 | // we at least try to give a correct error though. |
571 | match (date, time) { |
572 | (Err(PE(OutOfRange)), _) | (_, Err(PE(OutOfRange))) => return Err(OUT_OF_RANGE), |
573 | (Err(PE(Impossible)), _) | (_, Err(PE(Impossible))) => return Err(IMPOSSIBLE), |
574 | (_, _) => {} // one of them is insufficient |
575 | } |
576 | |
577 | // reconstruct date and time fields from timestamp |
578 | let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; |
579 | let datetime = NaiveDateTime::from_timestamp_opt(ts, 0); |
580 | let mut datetime = datetime.ok_or(OUT_OF_RANGE)?; |
581 | |
582 | // fill year, ordinal, hour, minute and second fields from timestamp. |
583 | // if existing fields are consistent, this will allow the full date/time reconstruction. |
584 | let mut parsed = self.clone(); |
585 | if parsed.second == Some(60) { |
586 | // `datetime.second()` cannot be 60, so this is the only case for a leap second. |
587 | match datetime.second() { |
588 | // it's okay, just do not try to overwrite the existing field. |
589 | 59 => {} |
590 | // `datetime` is known to be off by one second. |
591 | 0 => { |
592 | datetime -= OldDuration::seconds(1); |
593 | } |
594 | // otherwise it is impossible. |
595 | _ => return Err(IMPOSSIBLE), |
596 | } |
597 | // ...and we have the correct candidates for other fields. |
598 | } else { |
599 | parsed.set_second(i64::from(datetime.second()))?; |
600 | } |
601 | parsed.set_year(i64::from(datetime.year()))?; |
602 | parsed.set_ordinal(i64::from(datetime.ordinal()))?; // more efficient than ymd |
603 | parsed.set_hour(i64::from(datetime.hour()))?; |
604 | parsed.set_minute(i64::from(datetime.minute()))?; |
605 | |
606 | // validate other fields (e.g. week) and return |
607 | let date = parsed.to_naive_date()?; |
608 | let time = parsed.to_naive_time()?; |
609 | Ok(date.and_time(time)) |
610 | } else { |
611 | // reproduce the previous error(s) |
612 | date?; |
613 | time?; |
614 | unreachable!() |
615 | } |
616 | } |
617 | |
618 | /// Returns a parsed fixed time zone offset out of given fields. |
619 | pub fn to_fixed_offset(&self) -> ParseResult<FixedOffset> { |
620 | self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) |
621 | } |
622 | |
623 | /// Returns a parsed timezone-aware date and time out of given fields. |
624 | /// |
625 | /// This method is able to determine the combined date and time |
626 | /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, |
627 | /// plus a time zone offset. |
628 | /// Either way those fields have to be consistent to each other. |
629 | pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> { |
630 | let offset = self.offset.ok_or(NOT_ENOUGH)?; |
631 | let datetime = self.to_naive_datetime_with_offset(offset)?; |
632 | let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; |
633 | |
634 | // this is used to prevent an overflow when calling FixedOffset::from_local_datetime |
635 | datetime |
636 | .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) |
637 | .ok_or(OUT_OF_RANGE)?; |
638 | |
639 | match offset.from_local_datetime(&datetime) { |
640 | LocalResult::None => Err(IMPOSSIBLE), |
641 | LocalResult::Single(t) => Ok(t), |
642 | LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), |
643 | } |
644 | } |
645 | |
646 | /// Returns a parsed timezone-aware date and time out of given fields, |
647 | /// with an additional `TimeZone` used to interpret and validate the local date. |
648 | /// |
649 | /// This method is able to determine the combined date and time |
650 | /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, |
651 | /// plus a time zone offset. |
652 | /// Either way those fields have to be consistent to each other. |
653 | /// If parsed fields include an UTC offset, it also has to be consistent to |
654 | /// [`offset`](#structfield.offset). |
655 | pub fn to_datetime_with_timezone<Tz: TimeZone>(&self, tz: &Tz) -> ParseResult<DateTime<Tz>> { |
656 | // if we have `timestamp` specified, guess an offset from that. |
657 | let mut guessed_offset = 0; |
658 | if let Some(timestamp) = self.timestamp { |
659 | // make a naive `DateTime` from given timestamp and (if any) nanosecond. |
660 | // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. |
661 | let nanosecond = self.nanosecond.unwrap_or(0); |
662 | let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond); |
663 | let dt = dt.ok_or(OUT_OF_RANGE)?; |
664 | guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); |
665 | } |
666 | |
667 | // checks if the given `DateTime` has a consistent `Offset` with given `self.offset`. |
668 | let check_offset = |dt: &DateTime<Tz>| { |
669 | if let Some(offset) = self.offset { |
670 | dt.offset().fix().local_minus_utc() == offset |
671 | } else { |
672 | true |
673 | } |
674 | }; |
675 | |
676 | // `guessed_offset` should be correct when `self.timestamp` is given. |
677 | // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. |
678 | let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; |
679 | match tz.from_local_datetime(&datetime) { |
680 | LocalResult::None => Err(IMPOSSIBLE), |
681 | LocalResult::Single(t) => { |
682 | if check_offset(&t) { |
683 | Ok(t) |
684 | } else { |
685 | Err(IMPOSSIBLE) |
686 | } |
687 | } |
688 | LocalResult::Ambiguous(min, max) => { |
689 | // try to disambiguate two possible local dates by offset. |
690 | match (check_offset(&min), check_offset(&max)) { |
691 | (false, false) => Err(IMPOSSIBLE), |
692 | (false, true) => Ok(max), |
693 | (true, false) => Ok(min), |
694 | (true, true) => Err(NOT_ENOUGH), |
695 | } |
696 | } |
697 | } |
698 | } |
699 | } |
700 | |
701 | #[cfg (test)] |
702 | mod tests { |
703 | use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; |
704 | use super::Parsed; |
705 | use crate::naive::{NaiveDate, NaiveTime}; |
706 | use crate::offset::{FixedOffset, TimeZone, Utc}; |
707 | use crate::Datelike; |
708 | use crate::Weekday::*; |
709 | |
710 | #[test ] |
711 | fn test_parsed_set_fields() { |
712 | // year*, isoyear* |
713 | let mut p = Parsed::new(); |
714 | assert_eq!(p.set_year(1987), Ok(())); |
715 | assert_eq!(p.set_year(1986), Err(IMPOSSIBLE)); |
716 | assert_eq!(p.set_year(1988), Err(IMPOSSIBLE)); |
717 | assert_eq!(p.set_year(1987), Ok(())); |
718 | assert_eq!(p.set_year_div_100(20), Ok(())); // independent to `year` |
719 | assert_eq!(p.set_year_div_100(21), Err(IMPOSSIBLE)); |
720 | assert_eq!(p.set_year_div_100(19), Err(IMPOSSIBLE)); |
721 | assert_eq!(p.set_year_mod_100(37), Ok(())); // ditto |
722 | assert_eq!(p.set_year_mod_100(38), Err(IMPOSSIBLE)); |
723 | assert_eq!(p.set_year_mod_100(36), Err(IMPOSSIBLE)); |
724 | |
725 | let mut p = Parsed::new(); |
726 | assert_eq!(p.set_year(0), Ok(())); |
727 | assert_eq!(p.set_year_div_100(0), Ok(())); |
728 | assert_eq!(p.set_year_mod_100(0), Ok(())); |
729 | |
730 | let mut p = Parsed::new(); |
731 | assert_eq!(p.set_year_div_100(-1), Err(OUT_OF_RANGE)); |
732 | assert_eq!(p.set_year_mod_100(-1), Err(OUT_OF_RANGE)); |
733 | assert_eq!(p.set_year(-1), Ok(())); |
734 | assert_eq!(p.set_year(-2), Err(IMPOSSIBLE)); |
735 | assert_eq!(p.set_year(0), Err(IMPOSSIBLE)); |
736 | |
737 | let mut p = Parsed::new(); |
738 | assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); |
739 | assert_eq!(p.set_year_div_100(8), Ok(())); |
740 | assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE)); |
741 | |
742 | // month, week*, isoweek, ordinal, day, minute, second, nanosecond, offset |
743 | let mut p = Parsed::new(); |
744 | assert_eq!(p.set_month(7), Ok(())); |
745 | assert_eq!(p.set_month(1), Err(IMPOSSIBLE)); |
746 | assert_eq!(p.set_month(6), Err(IMPOSSIBLE)); |
747 | assert_eq!(p.set_month(8), Err(IMPOSSIBLE)); |
748 | assert_eq!(p.set_month(12), Err(IMPOSSIBLE)); |
749 | |
750 | let mut p = Parsed::new(); |
751 | assert_eq!(p.set_month(8), Ok(())); |
752 | assert_eq!(p.set_month(0x1_0000_0008), Err(OUT_OF_RANGE)); |
753 | |
754 | // hour |
755 | let mut p = Parsed::new(); |
756 | assert_eq!(p.set_hour(12), Ok(())); |
757 | assert_eq!(p.set_hour(11), Err(IMPOSSIBLE)); |
758 | assert_eq!(p.set_hour(13), Err(IMPOSSIBLE)); |
759 | assert_eq!(p.set_hour(12), Ok(())); |
760 | assert_eq!(p.set_ampm(false), Err(IMPOSSIBLE)); |
761 | assert_eq!(p.set_ampm(true), Ok(())); |
762 | assert_eq!(p.set_hour12(12), Ok(())); |
763 | assert_eq!(p.set_hour12(0), Err(OUT_OF_RANGE)); // requires canonical representation |
764 | assert_eq!(p.set_hour12(1), Err(IMPOSSIBLE)); |
765 | assert_eq!(p.set_hour12(11), Err(IMPOSSIBLE)); |
766 | |
767 | let mut p = Parsed::new(); |
768 | assert_eq!(p.set_ampm(true), Ok(())); |
769 | assert_eq!(p.set_hour12(7), Ok(())); |
770 | assert_eq!(p.set_hour(7), Err(IMPOSSIBLE)); |
771 | assert_eq!(p.set_hour(18), Err(IMPOSSIBLE)); |
772 | assert_eq!(p.set_hour(19), Ok(())); |
773 | |
774 | // timestamp |
775 | let mut p = Parsed::new(); |
776 | assert_eq!(p.set_timestamp(1_234_567_890), Ok(())); |
777 | assert_eq!(p.set_timestamp(1_234_567_889), Err(IMPOSSIBLE)); |
778 | assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); |
779 | } |
780 | |
781 | #[test ] |
782 | fn test_parsed_to_naive_date() { |
783 | macro_rules! parse { |
784 | ($($k:ident: $v:expr),*) => ( |
785 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_date() |
786 | ) |
787 | } |
788 | |
789 | let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); |
790 | |
791 | // ymd: omission of fields |
792 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
793 | assert_eq!(parse!(year: 1984), Err(NOT_ENOUGH)); |
794 | assert_eq!(parse!(year: 1984, month: 1), Err(NOT_ENOUGH)); |
795 | assert_eq!(parse!(year: 1984, month: 1, day: 2), ymd(1984, 1, 2)); |
796 | assert_eq!(parse!(year: 1984, day: 2), Err(NOT_ENOUGH)); |
797 | assert_eq!(parse!(year_div_100: 19), Err(NOT_ENOUGH)); |
798 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84), Err(NOT_ENOUGH)); |
799 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1), Err(NOT_ENOUGH)); |
800 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1, day: 2), ymd(1984, 1, 2)); |
801 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, day: 2), Err(NOT_ENOUGH)); |
802 | assert_eq!(parse!(year_div_100: 19, month: 1, day: 2), Err(NOT_ENOUGH)); |
803 | assert_eq!(parse!(year_mod_100: 70, month: 1, day: 2), ymd(1970, 1, 2)); |
804 | assert_eq!(parse!(year_mod_100: 69, month: 1, day: 2), ymd(2069, 1, 2)); |
805 | |
806 | // ymd: out-of-range conditions |
807 | assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 2, day: 29), ymd(1984, 2, 29)); |
808 | assert_eq!( |
809 | parse!(year_div_100: 19, year_mod_100: 83, month: 2, day: 29), |
810 | Err(OUT_OF_RANGE) |
811 | ); |
812 | assert_eq!( |
813 | parse!(year_div_100: 19, year_mod_100: 83, month: 13, day: 1), |
814 | Err(OUT_OF_RANGE) |
815 | ); |
816 | assert_eq!( |
817 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 31), |
818 | ymd(1983, 12, 31) |
819 | ); |
820 | assert_eq!( |
821 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 32), |
822 | Err(OUT_OF_RANGE) |
823 | ); |
824 | assert_eq!( |
825 | parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 0), |
826 | Err(OUT_OF_RANGE) |
827 | ); |
828 | assert_eq!( |
829 | parse!(year_div_100: 19, year_mod_100: 100, month: 1, day: 1), |
830 | Err(OUT_OF_RANGE) |
831 | ); |
832 | assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); |
833 | assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); |
834 | assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE)); |
835 | let max_year = NaiveDate::MAX.year(); |
836 | assert_eq!( |
837 | parse!(year_div_100: max_year / 100, |
838 | year_mod_100: max_year % 100, month: 1, day: 1), |
839 | ymd(max_year, 1, 1) |
840 | ); |
841 | assert_eq!( |
842 | parse!(year_div_100: (max_year + 1) / 100, |
843 | year_mod_100: (max_year + 1) % 100, month: 1, day: 1), |
844 | Err(OUT_OF_RANGE) |
845 | ); |
846 | |
847 | // ymd: conflicting inputs |
848 | assert_eq!(parse!(year: 1984, year_div_100: 19, month: 1, day: 1), ymd(1984, 1, 1)); |
849 | assert_eq!(parse!(year: 1984, year_div_100: 20, month: 1, day: 1), Err(IMPOSSIBLE)); |
850 | assert_eq!(parse!(year: 1984, year_mod_100: 84, month: 1, day: 1), ymd(1984, 1, 1)); |
851 | assert_eq!(parse!(year: 1984, year_mod_100: 83, month: 1, day: 1), Err(IMPOSSIBLE)); |
852 | assert_eq!( |
853 | parse!(year: 1984, year_div_100: 19, year_mod_100: 84, month: 1, day: 1), |
854 | ymd(1984, 1, 1) |
855 | ); |
856 | assert_eq!( |
857 | parse!(year: 1984, year_div_100: 18, year_mod_100: 94, month: 1, day: 1), |
858 | Err(IMPOSSIBLE) |
859 | ); |
860 | assert_eq!( |
861 | parse!(year: 1984, year_div_100: 18, year_mod_100: 184, month: 1, day: 1), |
862 | Err(OUT_OF_RANGE) |
863 | ); |
864 | assert_eq!( |
865 | parse!(year: -1, year_div_100: 0, year_mod_100: -1, month: 1, day: 1), |
866 | Err(OUT_OF_RANGE) |
867 | ); |
868 | assert_eq!( |
869 | parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), |
870 | Err(OUT_OF_RANGE) |
871 | ); |
872 | assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE)); |
873 | assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE)); |
874 | |
875 | // weekdates |
876 | assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); |
877 | assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); |
878 | assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); |
879 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE)); |
880 | assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE)); |
881 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); |
882 | assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); |
883 | assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); |
884 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sun), ymd(2000, 1, 2)); |
885 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Mon), ymd(2000, 1, 3)); |
886 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Mon), ymd(2000, 1, 3)); |
887 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sat), ymd(2000, 1, 8)); |
888 | assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sat), ymd(2000, 1, 8)); |
889 | assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sun), ymd(2000, 1, 9)); |
890 | assert_eq!(parse!(year: 2000, week_from_sun: 2, weekday: Sun), ymd(2000, 1, 9)); |
891 | assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); |
892 | assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); |
893 | assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); |
894 | assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE)); |
895 | assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); |
896 | assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE)); |
897 | assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); |
898 | |
899 | // weekdates: conflicting inputs |
900 | assert_eq!( |
901 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sat), |
902 | ymd(2000, 1, 8) |
903 | ); |
904 | assert_eq!( |
905 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 2, weekday: Sun), |
906 | ymd(2000, 1, 9) |
907 | ); |
908 | assert_eq!( |
909 | parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sun), |
910 | Err(IMPOSSIBLE) |
911 | ); |
912 | assert_eq!( |
913 | parse!(year: 2000, week_from_mon: 2, week_from_sun: 2, weekday: Sun), |
914 | Err(IMPOSSIBLE) |
915 | ); |
916 | |
917 | // ISO weekdates |
918 | assert_eq!(parse!(isoyear: 2004, isoweek: 53), Err(NOT_ENOUGH)); |
919 | assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Fri), ymd(2004, 12, 31)); |
920 | assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Sat), ymd(2005, 1, 1)); |
921 | assert_eq!(parse!(isoyear: 2004, isoweek: 0xffffffff, weekday: Sat), Err(OUT_OF_RANGE)); |
922 | assert_eq!(parse!(isoyear: 2005, isoweek: 0, weekday: Thu), Err(OUT_OF_RANGE)); |
923 | assert_eq!(parse!(isoyear: 2005, isoweek: 5, weekday: Thu), ymd(2005, 2, 3)); |
924 | assert_eq!(parse!(isoyear: 2005, weekday: Thu), Err(NOT_ENOUGH)); |
925 | |
926 | // year and ordinal |
927 | assert_eq!(parse!(ordinal: 123), Err(NOT_ENOUGH)); |
928 | assert_eq!(parse!(year: 2000, ordinal: 0), Err(OUT_OF_RANGE)); |
929 | assert_eq!(parse!(year: 2000, ordinal: 1), ymd(2000, 1, 1)); |
930 | assert_eq!(parse!(year: 2000, ordinal: 60), ymd(2000, 2, 29)); |
931 | assert_eq!(parse!(year: 2000, ordinal: 61), ymd(2000, 3, 1)); |
932 | assert_eq!(parse!(year: 2000, ordinal: 366), ymd(2000, 12, 31)); |
933 | assert_eq!(parse!(year: 2000, ordinal: 367), Err(OUT_OF_RANGE)); |
934 | assert_eq!(parse!(year: 2000, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); |
935 | assert_eq!(parse!(year: 2100, ordinal: 0), Err(OUT_OF_RANGE)); |
936 | assert_eq!(parse!(year: 2100, ordinal: 1), ymd(2100, 1, 1)); |
937 | assert_eq!(parse!(year: 2100, ordinal: 59), ymd(2100, 2, 28)); |
938 | assert_eq!(parse!(year: 2100, ordinal: 60), ymd(2100, 3, 1)); |
939 | assert_eq!(parse!(year: 2100, ordinal: 365), ymd(2100, 12, 31)); |
940 | assert_eq!(parse!(year: 2100, ordinal: 366), Err(OUT_OF_RANGE)); |
941 | assert_eq!(parse!(year: 2100, ordinal: 0xffffffff), Err(OUT_OF_RANGE)); |
942 | |
943 | // more complex cases |
944 | assert_eq!( |
945 | parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoweek: 1, |
946 | week_from_sun: 52, week_from_mon: 52, weekday: Wed), |
947 | ymd(2014, 12, 31) |
948 | ); |
949 | assert_eq!( |
950 | parse!(year: 2014, month: 12, ordinal: 365, isoyear: 2015, isoweek: 1, |
951 | week_from_sun: 52, week_from_mon: 52), |
952 | ymd(2014, 12, 31) |
953 | ); |
954 | assert_eq!( |
955 | parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2014, isoweek: 53, |
956 | week_from_sun: 52, week_from_mon: 52, weekday: Wed), |
957 | Err(IMPOSSIBLE) |
958 | ); // no ISO week date 2014-W53-3 |
959 | assert_eq!( |
960 | parse!(year: 2012, isoyear: 2015, isoweek: 1, |
961 | week_from_sun: 52, week_from_mon: 52), |
962 | Err(NOT_ENOUGH) |
963 | ); // ambiguous (2014-12-29, 2014-12-30, 2014-12-31) |
964 | assert_eq!(parse!(year_div_100: 20, isoyear_mod_100: 15, ordinal: 366), Err(NOT_ENOUGH)); |
965 | // technically unique (2014-12-31) but Chrono gives up |
966 | } |
967 | |
968 | #[test ] |
969 | fn test_parsed_to_naive_time() { |
970 | macro_rules! parse { |
971 | ($($k:ident: $v:expr),*) => ( |
972 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_time() |
973 | ) |
974 | } |
975 | |
976 | let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); |
977 | let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); |
978 | |
979 | // omission of fields |
980 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
981 | assert_eq!(parse!(hour_div_12: 0), Err(NOT_ENOUGH)); |
982 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1), Err(NOT_ENOUGH)); |
983 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23), hms(1, 23, 0)); |
984 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45), hms(1, 23, 45)); |
985 | assert_eq!( |
986 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45, |
987 | nanosecond: 678_901_234), |
988 | hmsn(1, 23, 45, 678_901_234) |
989 | ); |
990 | assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6), hms(23, 45, 6)); |
991 | assert_eq!(parse!(hour_mod_12: 1, minute: 23), Err(NOT_ENOUGH)); |
992 | assert_eq!( |
993 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, nanosecond: 456_789_012), |
994 | Err(NOT_ENOUGH) |
995 | ); |
996 | |
997 | // out-of-range conditions |
998 | assert_eq!(parse!(hour_div_12: 2, hour_mod_12: 0, minute: 0), Err(OUT_OF_RANGE)); |
999 | assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 12, minute: 0), Err(OUT_OF_RANGE)); |
1000 | assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 60), Err(OUT_OF_RANGE)); |
1001 | assert_eq!( |
1002 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 61), |
1003 | Err(OUT_OF_RANGE) |
1004 | ); |
1005 | assert_eq!( |
1006 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 34, |
1007 | nanosecond: 1_000_000_000), |
1008 | Err(OUT_OF_RANGE) |
1009 | ); |
1010 | |
1011 | // leap seconds |
1012 | assert_eq!( |
1013 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60), |
1014 | hmsn(1, 23, 59, 1_000_000_000) |
1015 | ); |
1016 | assert_eq!( |
1017 | parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60, |
1018 | nanosecond: 999_999_999), |
1019 | hmsn(1, 23, 59, 1_999_999_999) |
1020 | ); |
1021 | } |
1022 | |
1023 | #[test ] |
1024 | fn test_parsed_to_naive_datetime_with_offset() { |
1025 | macro_rules! parse { |
1026 | (offset = $offset:expr; $($k:ident: $v:expr),*) => ( |
1027 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_datetime_with_offset($offset) |
1028 | ); |
1029 | ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) |
1030 | } |
1031 | |
1032 | let ymdhms = |y, m, d, h, n, s| { |
1033 | Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) |
1034 | }; |
1035 | let ymdhmsn = |y, m, d, h, n, s, nano| { |
1036 | Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) |
1037 | }; |
1038 | |
1039 | // omission of fields |
1040 | assert_eq!(parse!(), Err(NOT_ENOUGH)); |
1041 | assert_eq!( |
1042 | parse!(year: 2015, month: 1, day: 30, |
1043 | hour_div_12: 1, hour_mod_12: 2, minute: 38), |
1044 | ymdhms(2015, 1, 30, 14, 38, 0) |
1045 | ); |
1046 | assert_eq!( |
1047 | parse!(year: 1997, month: 1, day: 30, |
1048 | hour_div_12: 1, hour_mod_12: 2, minute: 38, second: 5), |
1049 | ymdhms(1997, 1, 30, 14, 38, 5) |
1050 | ); |
1051 | assert_eq!( |
1052 | parse!(year: 2012, ordinal: 34, hour_div_12: 0, hour_mod_12: 5, |
1053 | minute: 6, second: 7, nanosecond: 890_123_456), |
1054 | ymdhmsn(2012, 2, 3, 5, 6, 7, 890_123_456) |
1055 | ); |
1056 | assert_eq!(parse!(timestamp: 0), ymdhms(1970, 1, 1, 0, 0, 0)); |
1057 | assert_eq!(parse!(timestamp: 1, nanosecond: 0), ymdhms(1970, 1, 1, 0, 0, 1)); |
1058 | assert_eq!(parse!(timestamp: 1, nanosecond: 1), ymdhmsn(1970, 1, 1, 0, 0, 1, 1)); |
1059 | assert_eq!(parse!(timestamp: 1_420_000_000), ymdhms(2014, 12, 31, 4, 26, 40)); |
1060 | assert_eq!(parse!(timestamp: -0x1_0000_0000), ymdhms(1833, 11, 24, 17, 31, 44)); |
1061 | |
1062 | // full fields |
1063 | assert_eq!( |
1064 | parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
1065 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
1066 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
1067 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
1068 | nanosecond: 12_345_678, timestamp: 1_420_000_000), |
1069 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) |
1070 | ); |
1071 | assert_eq!( |
1072 | parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
1073 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
1074 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
1075 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
1076 | nanosecond: 12_345_678, timestamp: 1_419_999_999), |
1077 | Err(IMPOSSIBLE) |
1078 | ); |
1079 | assert_eq!( |
1080 | parse!(offset = 32400; |
1081 | year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31, |
1082 | ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15, |
1083 | isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed, |
1084 | hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, |
1085 | nanosecond: 12_345_678, timestamp: 1_419_967_600), |
1086 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678) |
1087 | ); |
1088 | |
1089 | // more timestamps |
1090 | let max_days_from_year_1970 = |
1091 | NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
1092 | let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) |
1093 | .unwrap() |
1094 | .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
1095 | let min_days_from_year_1970 = |
1096 | NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); |
1097 | assert_eq!( |
1098 | parse!(timestamp: min_days_from_year_1970.num_seconds()), |
1099 | ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) |
1100 | ); |
1101 | assert_eq!( |
1102 | parse!(timestamp: year_0_from_year_1970.num_seconds()), |
1103 | ymdhms(0, 1, 1, 0, 0, 0) |
1104 | ); |
1105 | assert_eq!( |
1106 | parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), |
1107 | ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) |
1108 | ); |
1109 | |
1110 | // leap seconds #1: partial fields |
1111 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_798), Err(IMPOSSIBLE)); |
1112 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_799), ymdhms(2012, 6, 30, 23, 59, 59)); |
1113 | assert_eq!(parse!(second: 59, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); |
1114 | assert_eq!( |
1115 | parse!(second: 60, timestamp: 1_341_100_799), |
1116 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
1117 | ); |
1118 | assert_eq!( |
1119 | parse!(second: 60, timestamp: 1_341_100_800), |
1120 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
1121 | ); |
1122 | assert_eq!(parse!(second: 0, timestamp: 1_341_100_800), ymdhms(2012, 7, 1, 0, 0, 0)); |
1123 | assert_eq!(parse!(second: 1, timestamp: 1_341_100_800), Err(IMPOSSIBLE)); |
1124 | assert_eq!(parse!(second: 60, timestamp: 1_341_100_801), Err(IMPOSSIBLE)); |
1125 | |
1126 | // leap seconds #2: full fields |
1127 | // we need to have separate tests for them since it uses another control flow. |
1128 | assert_eq!( |
1129 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1130 | minute: 59, second: 59, timestamp: 1_341_100_798), |
1131 | Err(IMPOSSIBLE) |
1132 | ); |
1133 | assert_eq!( |
1134 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1135 | minute: 59, second: 59, timestamp: 1_341_100_799), |
1136 | ymdhms(2012, 6, 30, 23, 59, 59) |
1137 | ); |
1138 | assert_eq!( |
1139 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1140 | minute: 59, second: 59, timestamp: 1_341_100_800), |
1141 | Err(IMPOSSIBLE) |
1142 | ); |
1143 | assert_eq!( |
1144 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1145 | minute: 59, second: 60, timestamp: 1_341_100_799), |
1146 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
1147 | ); |
1148 | assert_eq!( |
1149 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1150 | minute: 59, second: 60, timestamp: 1_341_100_800), |
1151 | ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000) |
1152 | ); |
1153 | assert_eq!( |
1154 | parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, |
1155 | minute: 0, second: 0, timestamp: 1_341_100_800), |
1156 | ymdhms(2012, 7, 1, 0, 0, 0) |
1157 | ); |
1158 | assert_eq!( |
1159 | parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0, |
1160 | minute: 0, second: 1, timestamp: 1_341_100_800), |
1161 | Err(IMPOSSIBLE) |
1162 | ); |
1163 | assert_eq!( |
1164 | parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11, |
1165 | minute: 59, second: 60, timestamp: 1_341_100_801), |
1166 | Err(IMPOSSIBLE) |
1167 | ); |
1168 | |
1169 | // error codes |
1170 | assert_eq!( |
1171 | parse!(year: 2015, month: 1, day: 20, weekday: Tue, |
1172 | hour_div_12: 2, hour_mod_12: 1, minute: 35, second: 20), |
1173 | Err(OUT_OF_RANGE) |
1174 | ); // `hour_div_12` is out of range |
1175 | } |
1176 | |
1177 | #[test ] |
1178 | fn test_parsed_to_datetime() { |
1179 | macro_rules! parse { |
1180 | ($($k:ident: $v:expr),*) => ( |
1181 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime() |
1182 | ) |
1183 | } |
1184 | |
1185 | let ymdhmsn = |y, m, d, h, n, s, nano, off| { |
1186 | Ok(FixedOffset::east_opt(off) |
1187 | .unwrap() |
1188 | .from_local_datetime( |
1189 | &NaiveDate::from_ymd_opt(y, m, d) |
1190 | .unwrap() |
1191 | .and_hms_nano_opt(h, n, s, nano) |
1192 | .unwrap(), |
1193 | ) |
1194 | .unwrap()) |
1195 | }; |
1196 | |
1197 | assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); |
1198 | assert_eq!( |
1199 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
1200 | minute: 26, second: 40, nanosecond: 12_345_678), |
1201 | Err(NOT_ENOUGH) |
1202 | ); |
1203 | assert_eq!( |
1204 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
1205 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
1206 | ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678, 0) |
1207 | ); |
1208 | assert_eq!( |
1209 | parse!(year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
1210 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
1211 | ymdhmsn(2014, 12, 31, 13, 26, 40, 12_345_678, 32400) |
1212 | ); |
1213 | assert_eq!( |
1214 | parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 1, |
1215 | minute: 42, second: 4, nanosecond: 12_345_678, offset: -9876), |
1216 | ymdhmsn(2014, 12, 31, 1, 42, 4, 12_345_678, -9876) |
1217 | ); |
1218 | assert_eq!( |
1219 | parse!(year: 2015, ordinal: 1, hour_div_12: 0, hour_mod_12: 4, |
1220 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 86_400), |
1221 | Err(OUT_OF_RANGE) |
1222 | ); // `FixedOffset` does not support such huge offset |
1223 | } |
1224 | |
1225 | #[test ] |
1226 | fn test_parsed_to_datetime_with_timezone() { |
1227 | macro_rules! parse { |
1228 | ($tz:expr; $($k:ident: $v:expr),*) => ( |
1229 | Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime_with_timezone(&$tz) |
1230 | ) |
1231 | } |
1232 | |
1233 | // single result from ymdhms |
1234 | assert_eq!( |
1235 | parse!(Utc; |
1236 | year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
1237 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
1238 | Ok(Utc |
1239 | .from_local_datetime( |
1240 | &NaiveDate::from_ymd_opt(2014, 12, 31) |
1241 | .unwrap() |
1242 | .and_hms_nano_opt(4, 26, 40, 12_345_678) |
1243 | .unwrap() |
1244 | ) |
1245 | .unwrap()) |
1246 | ); |
1247 | assert_eq!( |
1248 | parse!(Utc; |
1249 | year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
1250 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
1251 | Err(IMPOSSIBLE) |
1252 | ); |
1253 | assert_eq!( |
1254 | parse!(FixedOffset::east_opt(32400).unwrap(); |
1255 | year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, |
1256 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), |
1257 | Err(IMPOSSIBLE) |
1258 | ); |
1259 | assert_eq!( |
1260 | parse!(FixedOffset::east_opt(32400).unwrap(); |
1261 | year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, |
1262 | minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), |
1263 | Ok(FixedOffset::east_opt(32400) |
1264 | .unwrap() |
1265 | .from_local_datetime( |
1266 | &NaiveDate::from_ymd_opt(2014, 12, 31) |
1267 | .unwrap() |
1268 | .and_hms_nano_opt(13, 26, 40, 12_345_678) |
1269 | .unwrap() |
1270 | ) |
1271 | .unwrap()) |
1272 | ); |
1273 | |
1274 | // single result from timestamp |
1275 | assert_eq!( |
1276 | parse!(Utc; timestamp: 1_420_000_000, offset: 0), |
1277 | Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) |
1278 | ); |
1279 | assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); |
1280 | assert_eq!( |
1281 | parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), |
1282 | Err(IMPOSSIBLE) |
1283 | ); |
1284 | assert_eq!( |
1285 | parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), |
1286 | Ok(FixedOffset::east_opt(32400) |
1287 | .unwrap() |
1288 | .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) |
1289 | .unwrap()) |
1290 | ); |
1291 | |
1292 | // TODO test with a variable time zone (for None and Ambiguous cases) |
1293 | } |
1294 | |
1295 | #[test ] |
1296 | fn issue_551() { |
1297 | use crate::Weekday; |
1298 | let mut parsed = Parsed::new(); |
1299 | |
1300 | parsed.year = Some(2002); |
1301 | parsed.week_from_mon = Some(22); |
1302 | parsed.weekday = Some(Weekday::Mon); |
1303 | assert_eq!(NaiveDate::from_ymd_opt(2002, 6, 3).unwrap(), parsed.to_naive_date().unwrap()); |
1304 | |
1305 | parsed.year = Some(2001); |
1306 | assert_eq!(NaiveDate::from_ymd_opt(2001, 5, 28).unwrap(), parsed.to_naive_date().unwrap()); |
1307 | } |
1308 | } |
1309 | |