1 | // This is a part of Chrono. |
2 | // Portions copyright (c) 2015, John Nagle. |
3 | // See README.md and LICENSE.txt for details. |
4 | |
5 | //! Date and time parsing routines. |
6 | |
7 | #![allow (deprecated)] |
8 | |
9 | use core::borrow::Borrow; |
10 | use core::str; |
11 | use core::usize; |
12 | |
13 | use super::scan; |
14 | use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; |
15 | use super::{ParseError, ParseErrorKind, ParseResult}; |
16 | use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; |
17 | use crate::{DateTime, FixedOffset, Weekday}; |
18 | |
19 | fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { |
20 | p.set_weekday(match v { |
21 | 0 => Weekday::Sun, |
22 | 1 => Weekday::Mon, |
23 | 2 => Weekday::Tue, |
24 | 3 => Weekday::Wed, |
25 | 4 => Weekday::Thu, |
26 | 5 => Weekday::Fri, |
27 | 6 => Weekday::Sat, |
28 | _ => return Err(OUT_OF_RANGE), |
29 | }) |
30 | } |
31 | |
32 | fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<()> { |
33 | p.set_weekday(match v { |
34 | 1 => Weekday::Mon, |
35 | 2 => Weekday::Tue, |
36 | 3 => Weekday::Wed, |
37 | 4 => Weekday::Thu, |
38 | 5 => Weekday::Fri, |
39 | 6 => Weekday::Sat, |
40 | 7 => Weekday::Sun, |
41 | _ => return Err(OUT_OF_RANGE), |
42 | }) |
43 | } |
44 | |
45 | fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { |
46 | macro_rules! try_consume { |
47 | ($e:expr) => {{ |
48 | let (s_, v) = $e?; |
49 | s = s_; |
50 | v |
51 | }}; |
52 | } |
53 | |
54 | // an adapted RFC 2822 syntax from Section 3.3 and 4.3: |
55 | // |
56 | // c-char = <any char except '(', ')' and '\\'> |
57 | // c-escape = "\" <any char> |
58 | // comment = "(" *(comment / c-char / c-escape) ")" *S |
59 | // date-time = [ day-of-week "," ] date 1*S time *S *comment |
60 | // day-of-week = *S day-name *S |
61 | // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" |
62 | // date = day month year |
63 | // day = *S 1*2DIGIT *S |
64 | // month = 1*S month-name 1*S |
65 | // month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / |
66 | // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" |
67 | // year = *S 2*DIGIT *S |
68 | // time = time-of-day 1*S zone |
69 | // time-of-day = hour ":" minute [ ":" second ] |
70 | // hour = *S 2DIGIT *S |
71 | // minute = *S 2DIGIT *S |
72 | // second = *S 2DIGIT *S |
73 | // zone = ( "+" / "-" ) 4DIGIT / |
74 | // "UT" / "GMT" / ; same as +0000 |
75 | // "EST" / "CST" / "MST" / "PST" / ; same as -0500 to -0800 |
76 | // "EDT" / "CDT" / "MDT" / "PDT" / ; same as -0400 to -0700 |
77 | // 1*(%d65-90 / %d97-122) ; same as -0000 |
78 | // |
79 | // some notes: |
80 | // |
81 | // - quoted characters can be in any mixture of lower and upper cases. |
82 | // |
83 | // - we do not recognize a folding white space (FWS) or comment (CFWS). |
84 | // for our purposes, instead, we accept any sequence of Unicode |
85 | // white space characters (denoted here to `S`). For comments, we accept |
86 | // any text within parentheses while respecting escaped parentheses. |
87 | // Any actual RFC 2822 parser is expected to parse FWS and/or CFWS themselves |
88 | // and replace it with a single SP (`%x20`); this is legitimate. |
89 | // |
90 | // - two-digit year < 50 should be interpreted by adding 2000. |
91 | // two-digit year >= 50 or three-digit year should be interpreted |
92 | // by adding 1900. note that four-or-more-digit years less than 1000 |
93 | // are *never* affected by this rule. |
94 | // |
95 | // - mismatching day-of-week is always an error, which is consistent to |
96 | // Chrono's own rules. |
97 | // |
98 | // - zones can range from `-9959` to `+9959`, but `FixedOffset` does not |
99 | // support offsets larger than 24 hours. this is not *that* problematic |
100 | // since we do not directly go to a `DateTime` so one can recover |
101 | // the offset information from `Parsed` anyway. |
102 | |
103 | s = s.trim_left(); |
104 | |
105 | if let Ok((s_, weekday)) = scan::short_weekday(s) { |
106 | if !s_.starts_with(',' ) { |
107 | return Err(INVALID); |
108 | } |
109 | s = &s_[1..]; |
110 | parsed.set_weekday(weekday)?; |
111 | } |
112 | |
113 | s = s.trim_left(); |
114 | parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; |
115 | s = scan::space(s)?; // mandatory |
116 | parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; |
117 | s = scan::space(s)?; // mandatory |
118 | |
119 | // distinguish two- and three-digit years from four-digit years |
120 | let prevlen = s.len(); |
121 | let mut year = try_consume!(scan::number(s, 2, usize::MAX)); |
122 | let yearlen = prevlen - s.len(); |
123 | match (yearlen, year) { |
124 | (2, 0..=49) => { |
125 | year += 2000; |
126 | } // 47 -> 2047, 05 -> 2005 |
127 | (2, 50..=99) => { |
128 | year += 1900; |
129 | } // 79 -> 1979 |
130 | (3, _) => { |
131 | year += 1900; |
132 | } // 112 -> 2012, 009 -> 1909 |
133 | (_, _) => {} // 1987 -> 1987, 0654 -> 0654 |
134 | } |
135 | parsed.set_year(year)?; |
136 | |
137 | s = scan::space(s)?; // mandatory |
138 | parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; |
139 | s = scan::char(s.trim_left(), b':' )?.trim_left(); // *S ":" *S |
140 | parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; |
141 | if let Ok(s_) = scan::char(s.trim_left(), b':' ) { |
142 | // [ ":" *S 2DIGIT ] |
143 | parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; |
144 | } |
145 | |
146 | s = scan::space(s)?; // mandatory |
147 | if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { |
148 | // only set the offset when it is definitely known (i.e. not `-0000`) |
149 | parsed.set_offset(i64::from(offset))?; |
150 | } |
151 | |
152 | // optional comments |
153 | while let Ok((s_out, ())) = scan::comment_2822(s) { |
154 | s = s_out; |
155 | } |
156 | |
157 | Ok((s, ())) |
158 | } |
159 | |
160 | fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { |
161 | macro_rules! try_consume { |
162 | ($e:expr) => {{ |
163 | let (s_, v) = $e?; |
164 | s = s_; |
165 | v |
166 | }}; |
167 | } |
168 | |
169 | // an adapted RFC 3339 syntax from Section 5.6: |
170 | // |
171 | // date-fullyear = 4DIGIT |
172 | // date-month = 2DIGIT ; 01-12 |
173 | // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year |
174 | // time-hour = 2DIGIT ; 00-23 |
175 | // time-minute = 2DIGIT ; 00-59 |
176 | // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules |
177 | // time-secfrac = "." 1*DIGIT |
178 | // time-numoffset = ("+" / "-") time-hour ":" time-minute |
179 | // time-offset = "Z" / time-numoffset |
180 | // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] |
181 | // full-date = date-fullyear "-" date-month "-" date-mday |
182 | // full-time = partial-time time-offset |
183 | // date-time = full-date "T" full-time |
184 | // |
185 | // some notes: |
186 | // |
187 | // - quoted characters can be in any mixture of lower and upper cases. |
188 | // |
189 | // - it may accept any number of fractional digits for seconds. |
190 | // for Chrono, this means that we should skip digits past first 9 digits. |
191 | // |
192 | // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. |
193 | // note that this restriction is unique to RFC 3339 and not ISO 8601. |
194 | // since this is not a typical Chrono behavior, we check it earlier. |
195 | |
196 | parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; |
197 | s = scan::char(s, b'-' )?; |
198 | parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; |
199 | s = scan::char(s, b'-' )?; |
200 | parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; |
201 | |
202 | s = match s.as_bytes().first() { |
203 | Some(&b't' ) | Some(&b'T' ) => &s[1..], |
204 | Some(_) => return Err(INVALID), |
205 | None => return Err(TOO_SHORT), |
206 | }; |
207 | |
208 | parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; |
209 | s = scan::char(s, b':' )?; |
210 | parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; |
211 | s = scan::char(s, b':' )?; |
212 | parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; |
213 | if s.starts_with('.' ) { |
214 | let nanosecond = try_consume!(scan::nanosecond(&s[1..])); |
215 | parsed.set_nanosecond(nanosecond)?; |
216 | } |
217 | |
218 | let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':' ))); |
219 | if offset <= -86_400 || offset >= 86_400 { |
220 | return Err(OUT_OF_RANGE); |
221 | } |
222 | parsed.set_offset(i64::from(offset))?; |
223 | |
224 | Ok((s, ())) |
225 | } |
226 | |
227 | /// Tries to parse given string into `parsed` with given formatting items. |
228 | /// Returns `Ok` when the entire string has been parsed (otherwise `parsed` should not be used). |
229 | /// There should be no trailing string after parsing; |
230 | /// use a stray [`Item::Space`](./enum.Item.html#variant.Space) to trim whitespaces. |
231 | /// |
232 | /// This particular date and time parser is: |
233 | /// |
234 | /// - Greedy. It will consume the longest possible prefix. |
235 | /// For example, `April` is always consumed entirely when the long month name is requested; |
236 | /// it equally accepts `Apr`, but prefers the longer prefix in this case. |
237 | /// |
238 | /// - Padding-agnostic (for numeric items). |
239 | /// The [`Pad`](./enum.Pad.html) field is completely ignored, |
240 | /// so one can prepend any number of whitespace then any number of zeroes before numbers. |
241 | /// |
242 | /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. |
243 | pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> |
244 | where |
245 | I: Iterator<Item = B>, |
246 | B: Borrow<Item<'a>>, |
247 | { |
248 | parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s: &str, e: ParseError)| e) |
249 | } |
250 | |
251 | /// Tries to parse given string into `parsed` with given formatting items. |
252 | /// Returns `Ok` with a slice of the unparsed remainder. |
253 | /// |
254 | /// This particular date and time parser is: |
255 | /// |
256 | /// - Greedy. It will consume the longest possible prefix. |
257 | /// For example, `April` is always consumed entirely when the long month name is requested; |
258 | /// it equally accepts `Apr`, but prefers the longer prefix in this case. |
259 | /// |
260 | /// - Padding-agnostic (for numeric items). |
261 | /// The [`Pad`](./enum.Pad.html) field is completely ignored, |
262 | /// so one can prepend any number of zeroes before numbers. |
263 | /// |
264 | /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. |
265 | pub fn parse_and_remainder<'a, 'b, I, B>( |
266 | parsed: &mut Parsed, |
267 | s: &'b str, |
268 | items: I, |
269 | ) -> ParseResult<&'b str> |
270 | where |
271 | I: Iterator<Item = B>, |
272 | B: Borrow<Item<'a>>, |
273 | { |
274 | match parse_internal(parsed, s, items) { |
275 | Ok(s: &str) => Ok(s), |
276 | Err((s: &str, ParseError(ParseErrorKind::TooLong))) => Ok(s), |
277 | Err((_s: &str, e: ParseError)) => Err(e), |
278 | } |
279 | } |
280 | |
281 | fn parse_internal<'a, 'b, I, B>( |
282 | parsed: &mut Parsed, |
283 | mut s: &'b str, |
284 | items: I, |
285 | ) -> Result<&'b str, (&'b str, ParseError)> |
286 | where |
287 | I: Iterator<Item = B>, |
288 | B: Borrow<Item<'a>>, |
289 | { |
290 | macro_rules! try_consume { |
291 | ($e:expr) => {{ |
292 | match $e { |
293 | Ok((s_, v)) => { |
294 | s = s_; |
295 | v |
296 | } |
297 | Err(e) => return Err((s, e)), |
298 | } |
299 | }}; |
300 | } |
301 | |
302 | for item in items { |
303 | match *item.borrow() { |
304 | Item::Literal(prefix) => { |
305 | if s.len() < prefix.len() { |
306 | return Err((s, TOO_SHORT)); |
307 | } |
308 | if !s.starts_with(prefix) { |
309 | return Err((s, INVALID)); |
310 | } |
311 | s = &s[prefix.len()..]; |
312 | } |
313 | |
314 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
315 | Item::OwnedLiteral(ref prefix) => { |
316 | if s.len() < prefix.len() { |
317 | return Err((s, TOO_SHORT)); |
318 | } |
319 | if !s.starts_with(&prefix[..]) { |
320 | return Err((s, INVALID)); |
321 | } |
322 | s = &s[prefix.len()..]; |
323 | } |
324 | |
325 | Item::Space(_) => { |
326 | s = s.trim_left(); |
327 | } |
328 | |
329 | #[cfg (any(feature = "alloc" , feature = "std" , test))] |
330 | Item::OwnedSpace(_) => { |
331 | s = s.trim_left(); |
332 | } |
333 | |
334 | Item::Numeric(ref spec, ref _pad) => { |
335 | use super::Numeric::*; |
336 | type Setter = fn(&mut Parsed, i64) -> ParseResult<()>; |
337 | |
338 | let (width, signed, set): (usize, bool, Setter) = match *spec { |
339 | Year => (4, true, Parsed::set_year), |
340 | YearDiv100 => (2, false, Parsed::set_year_div_100), |
341 | YearMod100 => (2, false, Parsed::set_year_mod_100), |
342 | IsoYear => (4, true, Parsed::set_isoyear), |
343 | IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), |
344 | IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), |
345 | Month => (2, false, Parsed::set_month), |
346 | Day => (2, false, Parsed::set_day), |
347 | WeekFromSun => (2, false, Parsed::set_week_from_sun), |
348 | WeekFromMon => (2, false, Parsed::set_week_from_mon), |
349 | IsoWeek => (2, false, Parsed::set_isoweek), |
350 | NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday), |
351 | WeekdayFromMon => (1, false, set_weekday_with_number_from_monday), |
352 | Ordinal => (3, false, Parsed::set_ordinal), |
353 | Hour => (2, false, Parsed::set_hour), |
354 | Hour12 => (2, false, Parsed::set_hour12), |
355 | Minute => (2, false, Parsed::set_minute), |
356 | Second => (2, false, Parsed::set_second), |
357 | Nanosecond => (9, false, Parsed::set_nanosecond), |
358 | Timestamp => (usize::MAX, false, Parsed::set_timestamp), |
359 | |
360 | // for the future expansion |
361 | Internal(ref int) => match int._dummy {}, |
362 | }; |
363 | |
364 | s = s.trim_left(); |
365 | let v = if signed { |
366 | if s.starts_with('-' ) { |
367 | let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); |
368 | 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))? |
369 | } else if s.starts_with('+' ) { |
370 | try_consume!(scan::number(&s[1..], 1, usize::MAX)) |
371 | } else { |
372 | // if there is no explicit sign, we respect the original `width` |
373 | try_consume!(scan::number(s, 1, width)) |
374 | } |
375 | } else { |
376 | try_consume!(scan::number(s, 1, width)) |
377 | }; |
378 | set(parsed, v).map_err(|e| (s, e))?; |
379 | } |
380 | |
381 | Item::Fixed(ref spec) => { |
382 | use super::Fixed::*; |
383 | |
384 | match spec { |
385 | &ShortMonthName => { |
386 | let month0 = try_consume!(scan::short_month0(s)); |
387 | parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; |
388 | } |
389 | |
390 | &LongMonthName => { |
391 | let month0 = try_consume!(scan::short_or_long_month0(s)); |
392 | parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; |
393 | } |
394 | |
395 | &ShortWeekdayName => { |
396 | let weekday = try_consume!(scan::short_weekday(s)); |
397 | parsed.set_weekday(weekday).map_err(|e| (s, e))?; |
398 | } |
399 | |
400 | &LongWeekdayName => { |
401 | let weekday = try_consume!(scan::short_or_long_weekday(s)); |
402 | parsed.set_weekday(weekday).map_err(|e| (s, e))?; |
403 | } |
404 | |
405 | &LowerAmPm | &UpperAmPm => { |
406 | if s.len() < 2 { |
407 | return Err((s, TOO_SHORT)); |
408 | } |
409 | let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { |
410 | (b'a' , b'm' ) => false, |
411 | (b'p' , b'm' ) => true, |
412 | _ => return Err((s, INVALID)), |
413 | }; |
414 | parsed.set_ampm(ampm).map_err(|e| (s, e))?; |
415 | s = &s[2..]; |
416 | } |
417 | |
418 | &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { |
419 | if s.starts_with('.' ) { |
420 | let nano = try_consume!(scan::nanosecond(&s[1..])); |
421 | parsed.set_nanosecond(nano).map_err(|e| (s, e))?; |
422 | } |
423 | } |
424 | |
425 | &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { |
426 | if s.len() < 3 { |
427 | return Err((s, TOO_SHORT)); |
428 | } |
429 | let nano = try_consume!(scan::nanosecond_fixed(s, 3)); |
430 | parsed.set_nanosecond(nano).map_err(|e| (s, e))?; |
431 | } |
432 | |
433 | &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { |
434 | if s.len() < 6 { |
435 | return Err((s, TOO_SHORT)); |
436 | } |
437 | let nano = try_consume!(scan::nanosecond_fixed(s, 6)); |
438 | parsed.set_nanosecond(nano).map_err(|e| (s, e))?; |
439 | } |
440 | |
441 | &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { |
442 | if s.len() < 9 { |
443 | return Err((s, TOO_SHORT)); |
444 | } |
445 | let nano = try_consume!(scan::nanosecond_fixed(s, 9)); |
446 | parsed.set_nanosecond(nano).map_err(|e| (s, e))?; |
447 | } |
448 | |
449 | &TimezoneName => { |
450 | try_consume!(scan::timezone_name_skip(s)); |
451 | } |
452 | |
453 | &TimezoneOffsetColon |
454 | | &TimezoneOffsetDoubleColon |
455 | | &TimezoneOffsetTripleColon |
456 | | &TimezoneOffset => { |
457 | let offset = try_consume!(scan::timezone_offset( |
458 | s.trim_left(), |
459 | scan::colon_or_space |
460 | )); |
461 | parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; |
462 | } |
463 | |
464 | &TimezoneOffsetColonZ | &TimezoneOffsetZ => { |
465 | let offset = try_consume!(scan::timezone_offset_zulu( |
466 | s.trim_left(), |
467 | scan::colon_or_space |
468 | )); |
469 | parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; |
470 | } |
471 | &Internal(InternalFixed { |
472 | val: InternalInternal::TimezoneOffsetPermissive, |
473 | }) => { |
474 | let offset = try_consume!(scan::timezone_offset_permissive( |
475 | s.trim_left(), |
476 | scan::colon_or_space |
477 | )); |
478 | parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; |
479 | } |
480 | |
481 | &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), |
482 | &RFC3339 => try_consume!(parse_rfc3339(parsed, s)), |
483 | } |
484 | } |
485 | |
486 | Item::Error => { |
487 | return Err((s, BAD_FORMAT)); |
488 | } |
489 | } |
490 | } |
491 | |
492 | // if there are trailling chars, it is an error |
493 | if !s.is_empty() { |
494 | Err((s, TOO_LONG)) |
495 | } else { |
496 | Ok(s) |
497 | } |
498 | } |
499 | |
500 | /// Accepts a relaxed form of RFC3339. |
501 | /// A space or a 'T' are acepted as the separator between the date and time |
502 | /// parts. Additional spaces are allowed between each component. |
503 | /// |
504 | /// All of these examples are equivalent: |
505 | /// ``` |
506 | /// # use chrono::{DateTime, offset::FixedOffset}; |
507 | /// "2012-12-12T12:12:12Z" .parse::<DateTime<FixedOffset>>()?; |
508 | /// "2012-12-12 12:12:12Z" .parse::<DateTime<FixedOffset>>()?; |
509 | /// "2012- 12-12T12: 12:12Z" .parse::<DateTime<FixedOffset>>()?; |
510 | /// # Ok::<(), chrono::ParseError>(()) |
511 | /// ``` |
512 | impl str::FromStr for DateTime<FixedOffset> { |
513 | type Err = ParseError; |
514 | |
515 | fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> { |
516 | const DATE_ITEMS: &[Item<'static>] = &[ |
517 | Item::Numeric(Numeric::Year, Pad::Zero), |
518 | Item::Space("" ), |
519 | Item::Literal("-" ), |
520 | Item::Numeric(Numeric::Month, Pad::Zero), |
521 | Item::Space("" ), |
522 | Item::Literal("-" ), |
523 | Item::Numeric(Numeric::Day, Pad::Zero), |
524 | ]; |
525 | const TIME_ITEMS: &[Item<'static>] = &[ |
526 | Item::Numeric(Numeric::Hour, Pad::Zero), |
527 | Item::Space("" ), |
528 | Item::Literal(":" ), |
529 | Item::Numeric(Numeric::Minute, Pad::Zero), |
530 | Item::Space("" ), |
531 | Item::Literal(":" ), |
532 | Item::Numeric(Numeric::Second, Pad::Zero), |
533 | Item::Fixed(Fixed::Nanosecond), |
534 | Item::Space("" ), |
535 | Item::Fixed(Fixed::TimezoneOffsetZ), |
536 | Item::Space("" ), |
537 | ]; |
538 | |
539 | let mut parsed = Parsed::new(); |
540 | match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) { |
541 | Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { |
542 | if remainder.starts_with('T' ) || remainder.starts_with(' ' ) { |
543 | parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; |
544 | } else { |
545 | return Err(INVALID); |
546 | } |
547 | } |
548 | Err((_s, e)) => return Err(e), |
549 | Ok(_) => return Err(NOT_ENOUGH), |
550 | }; |
551 | parsed.to_datetime() |
552 | } |
553 | } |
554 | |
555 | #[cfg (test)] |
556 | #[test ] |
557 | fn test_parse() { |
558 | use super::IMPOSSIBLE; |
559 | use super::*; |
560 | |
561 | // workaround for Rust issue #22255 |
562 | fn parse_all(s: &str, items: &[Item]) -> ParseResult<Parsed> { |
563 | let mut parsed = Parsed::new(); |
564 | parse(&mut parsed, s, items.iter())?; |
565 | Ok(parsed) |
566 | } |
567 | |
568 | macro_rules! check { |
569 | ($fmt:expr, $items:expr; $err:tt) => ( |
570 | assert_eq!(parse_all($fmt, &$items), Err($err)) |
571 | ); |
572 | ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { |
573 | let mut expected = Parsed::new(); |
574 | $(expected.$k = Some($v);)* |
575 | assert_eq!(parse_all($fmt, &$items), Ok(expected)) |
576 | }); |
577 | } |
578 | |
579 | // empty string |
580 | check!("" , []; ); |
581 | check!(" " , []; TOO_LONG); |
582 | check!("a" , []; TOO_LONG); |
583 | |
584 | // whitespaces |
585 | check!("" , [sp!("" )]; ); |
586 | check!(" " , [sp!("" )]; ); |
587 | check!(" \t" , [sp!("" )]; ); |
588 | check!(" \n\r \n" , [sp!("" )]; ); |
589 | check!("a" , [sp!("" )]; TOO_LONG); |
590 | |
591 | // literal |
592 | check!("" , [lit!("a" )]; TOO_SHORT); |
593 | check!(" " , [lit!("a" )]; INVALID); |
594 | check!("a" , [lit!("a" )]; ); |
595 | check!("aa" , [lit!("a" )]; TOO_LONG); |
596 | check!("A" , [lit!("a" )]; INVALID); |
597 | check!("xy" , [lit!("xy" )]; ); |
598 | check!("xy" , [lit!("x" ), lit!("y" )]; ); |
599 | check!("x y" , [lit!("x" ), lit!("y" )]; INVALID); |
600 | check!("xy" , [lit!("x" ), sp!("" ), lit!("y" )]; ); |
601 | check!("x y" , [lit!("x" ), sp!("" ), lit!("y" )]; ); |
602 | |
603 | // numeric |
604 | check!("1987" , [num!(Year)]; year: 1987); |
605 | check!("1987 " , [num!(Year)]; TOO_LONG); |
606 | check!("0x12" , [num!(Year)]; TOO_LONG); // `0` is parsed |
607 | check!("x123" , [num!(Year)]; INVALID); |
608 | check!("2015" , [num!(Year)]; year: 2015); |
609 | check!("0000" , [num!(Year)]; year: 0); |
610 | check!("9999" , [num!(Year)]; year: 9999); |
611 | check!(" \t987" , [num!(Year)]; year: 987); |
612 | check!("5" , [num!(Year)]; year: 5); |
613 | check!("5 \0" , [num!(Year)]; TOO_LONG); |
614 | check!(" \x005" , [num!(Year)]; INVALID); |
615 | check!("" , [num!(Year)]; TOO_SHORT); |
616 | check!("12345" , [num!(Year), lit!("5" )]; year: 1234); |
617 | check!("12345" , [nums!(Year), lit!("5" )]; year: 1234); |
618 | check!("12345" , [num0!(Year), lit!("5" )]; year: 1234); |
619 | check!("12341234" , [num!(Year), num!(Year)]; year: 1234); |
620 | check!("1234 1234" , [num!(Year), num!(Year)]; year: 1234); |
621 | check!("1234 1235" , [num!(Year), num!(Year)]; IMPOSSIBLE); |
622 | check!("1234 1234" , [num!(Year), lit!("x" ), num!(Year)]; INVALID); |
623 | check!("1234x1234" , [num!(Year), lit!("x" ), num!(Year)]; year: 1234); |
624 | check!("1234xx1234" , [num!(Year), lit!("x" ), num!(Year)]; INVALID); |
625 | check!("1234 x 1234" , [num!(Year), lit!("x" ), num!(Year)]; INVALID); |
626 | |
627 | // signed numeric |
628 | check!("-42" , [num!(Year)]; year: -42); |
629 | check!("+42" , [num!(Year)]; year: 42); |
630 | check!("-0042" , [num!(Year)]; year: -42); |
631 | check!("+0042" , [num!(Year)]; year: 42); |
632 | check!("-42195" , [num!(Year)]; year: -42195); |
633 | check!("+42195" , [num!(Year)]; year: 42195); |
634 | check!(" -42195" , [num!(Year)]; year: -42195); |
635 | check!(" +42195" , [num!(Year)]; year: 42195); |
636 | check!(" - 42" , [num!(Year)]; INVALID); |
637 | check!(" + 42" , [num!(Year)]; INVALID); |
638 | check!("-" , [num!(Year)]; TOO_SHORT); |
639 | check!("+" , [num!(Year)]; TOO_SHORT); |
640 | |
641 | // unsigned numeric |
642 | check!("345" , [num!(Ordinal)]; ordinal: 345); |
643 | check!("+345" , [num!(Ordinal)]; INVALID); |
644 | check!("-345" , [num!(Ordinal)]; INVALID); |
645 | check!(" 345" , [num!(Ordinal)]; ordinal: 345); |
646 | check!(" +345" , [num!(Ordinal)]; INVALID); |
647 | check!(" -345" , [num!(Ordinal)]; INVALID); |
648 | |
649 | // various numeric fields |
650 | check!("1234 5678" , |
651 | [num!(Year), num!(IsoYear)]; |
652 | year: 1234, isoyear: 5678); |
653 | check!("12 34 56 78" , |
654 | [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; |
655 | year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); |
656 | check!("1 2 3 4 5 6" , |
657 | [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), |
658 | num!(NumDaysFromSun)]; |
659 | month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); |
660 | check!("7 89 01" , |
661 | [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; |
662 | weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); |
663 | check!("23 45 6 78901234 567890123" , |
664 | [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; |
665 | hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, |
666 | timestamp: 567_890_123); |
667 | |
668 | // fixed: month and weekday names |
669 | check!("apr" , [fix!(ShortMonthName)]; month: 4); |
670 | check!("Apr" , [fix!(ShortMonthName)]; month: 4); |
671 | check!("APR" , [fix!(ShortMonthName)]; month: 4); |
672 | check!("ApR" , [fix!(ShortMonthName)]; month: 4); |
673 | check!("April" , [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed |
674 | check!("A" , [fix!(ShortMonthName)]; TOO_SHORT); |
675 | check!("Sol" , [fix!(ShortMonthName)]; INVALID); |
676 | check!("Apr" , [fix!(LongMonthName)]; month: 4); |
677 | check!("Apri" , [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed |
678 | check!("April" , [fix!(LongMonthName)]; month: 4); |
679 | check!("Aprill" , [fix!(LongMonthName)]; TOO_LONG); |
680 | check!("Aprill" , [fix!(LongMonthName), lit!("l" )]; month: 4); |
681 | check!("Aprl" , [fix!(LongMonthName), lit!("l" )]; month: 4); |
682 | check!("April" , [fix!(LongMonthName), lit!("il" )]; TOO_SHORT); // do not backtrack |
683 | check!("thu" , [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); |
684 | check!("Thu" , [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); |
685 | check!("THU" , [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); |
686 | check!("tHu" , [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); |
687 | check!("Thursday" , [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed |
688 | check!("T" , [fix!(ShortWeekdayName)]; TOO_SHORT); |
689 | check!("The" , [fix!(ShortWeekdayName)]; INVALID); |
690 | check!("Nop" , [fix!(ShortWeekdayName)]; INVALID); |
691 | check!("Thu" , [fix!(LongWeekdayName)]; weekday: Weekday::Thu); |
692 | check!("Thur" , [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed |
693 | check!("Thurs" , [fix!(LongWeekdayName)]; TOO_LONG); // ditto |
694 | check!("Thursday" , [fix!(LongWeekdayName)]; weekday: Weekday::Thu); |
695 | check!("Thursdays" , [fix!(LongWeekdayName)]; TOO_LONG); |
696 | check!("Thursdays" , [fix!(LongWeekdayName), lit!("s" )]; weekday: Weekday::Thu); |
697 | check!("Thus" , [fix!(LongWeekdayName), lit!("s" )]; weekday: Weekday::Thu); |
698 | check!("Thursday" , [fix!(LongWeekdayName), lit!("rsday" )]; TOO_SHORT); // do not backtrack |
699 | |
700 | // fixed: am/pm |
701 | check!("am" , [fix!(LowerAmPm)]; hour_div_12: 0); |
702 | check!("pm" , [fix!(LowerAmPm)]; hour_div_12: 1); |
703 | check!("AM" , [fix!(LowerAmPm)]; hour_div_12: 0); |
704 | check!("PM" , [fix!(LowerAmPm)]; hour_div_12: 1); |
705 | check!("am" , [fix!(UpperAmPm)]; hour_div_12: 0); |
706 | check!("pm" , [fix!(UpperAmPm)]; hour_div_12: 1); |
707 | check!("AM" , [fix!(UpperAmPm)]; hour_div_12: 0); |
708 | check!("PM" , [fix!(UpperAmPm)]; hour_div_12: 1); |
709 | check!("Am" , [fix!(LowerAmPm)]; hour_div_12: 0); |
710 | check!(" Am" , [fix!(LowerAmPm)]; INVALID); |
711 | check!("ame" , [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed |
712 | check!("a" , [fix!(LowerAmPm)]; TOO_SHORT); |
713 | check!("p" , [fix!(LowerAmPm)]; TOO_SHORT); |
714 | check!("x" , [fix!(LowerAmPm)]; TOO_SHORT); |
715 | check!("xx" , [fix!(LowerAmPm)]; INVALID); |
716 | check!("" , [fix!(LowerAmPm)]; TOO_SHORT); |
717 | |
718 | // fixed: dot plus nanoseconds |
719 | check!("" , [fix!(Nanosecond)]; ); // no field set, but not an error |
720 | check!("." , [fix!(Nanosecond)]; TOO_SHORT); |
721 | check!("4" , [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` |
722 | check!("4" , [fix!(Nanosecond), num!(Second)]; second: 4); |
723 | check!(".0" , [fix!(Nanosecond)]; nanosecond: 0); |
724 | check!(".4" , [fix!(Nanosecond)]; nanosecond: 400_000_000); |
725 | check!(".42" , [fix!(Nanosecond)]; nanosecond: 420_000_000); |
726 | check!(".421" , [fix!(Nanosecond)]; nanosecond: 421_000_000); |
727 | check!(".42195" , [fix!(Nanosecond)]; nanosecond: 421_950_000); |
728 | check!(".421950803" , [fix!(Nanosecond)]; nanosecond: 421_950_803); |
729 | check!(".421950803547" , [fix!(Nanosecond)]; nanosecond: 421_950_803); |
730 | check!(".000000003547" , [fix!(Nanosecond)]; nanosecond: 3); |
731 | check!(".000000000547" , [fix!(Nanosecond)]; nanosecond: 0); |
732 | check!("." , [fix!(Nanosecond)]; TOO_SHORT); |
733 | check!(".4x" , [fix!(Nanosecond)]; TOO_LONG); |
734 | check!(". 4" , [fix!(Nanosecond)]; INVALID); |
735 | check!(" .4" , [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming |
736 | |
737 | // fixed: nanoseconds without the dot |
738 | check!("" , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
739 | check!("." , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
740 | check!("0" , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
741 | check!("4" , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
742 | check!("42" , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
743 | check!("421" , [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); |
744 | check!("42143" , [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); |
745 | check!("42195" , [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); |
746 | check!("4x" , [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); |
747 | check!(" 4" , [internal_fix!(Nanosecond3NoDot)]; INVALID); |
748 | check!(".421" , [internal_fix!(Nanosecond3NoDot)]; INVALID); |
749 | |
750 | check!("" , [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); |
751 | check!("." , [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); |
752 | check!("0" , [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); |
753 | check!("42195" , [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); |
754 | check!("421950" , [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); |
755 | check!("000003" , [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); |
756 | check!("000000" , [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); |
757 | check!("4x" , [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); |
758 | check!(" 4" , [internal_fix!(Nanosecond6NoDot)]; INVALID); |
759 | check!(".42100" , [internal_fix!(Nanosecond6NoDot)]; INVALID); |
760 | |
761 | check!("" , [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); |
762 | check!("." , [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); |
763 | check!("42195" , [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); |
764 | check!("421950803" , [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); |
765 | check!("000000003" , [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); |
766 | check!("42195080354" , [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 |
767 | check!("421950803547" , [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); |
768 | check!("000000000" , [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); |
769 | check!("00000000x" , [internal_fix!(Nanosecond9NoDot)]; INVALID); |
770 | check!(" 4" , [internal_fix!(Nanosecond9NoDot)]; INVALID); |
771 | check!(".42100000" , [internal_fix!(Nanosecond9NoDot)]; INVALID); |
772 | |
773 | // fixed: timezone offsets |
774 | check!("+00:00" , [fix!(TimezoneOffset)]; offset: 0); |
775 | check!("-00:00" , [fix!(TimezoneOffset)]; offset: 0); |
776 | check!("+00:01" , [fix!(TimezoneOffset)]; offset: 60); |
777 | check!("-00:01" , [fix!(TimezoneOffset)]; offset: -60); |
778 | check!("+00:30" , [fix!(TimezoneOffset)]; offset: 30 * 60); |
779 | check!("-00:30" , [fix!(TimezoneOffset)]; offset: -30 * 60); |
780 | check!("+04:56" , [fix!(TimezoneOffset)]; offset: 296 * 60); |
781 | check!("-04:56" , [fix!(TimezoneOffset)]; offset: -296 * 60); |
782 | check!("+24:00" , [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); |
783 | check!("-24:00" , [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); |
784 | check!("+99:59" , [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); |
785 | check!("-99:59" , [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); |
786 | check!("+00:59" , [fix!(TimezoneOffset)]; offset: 59 * 60); |
787 | check!("+00:60" , [fix!(TimezoneOffset)]; OUT_OF_RANGE); |
788 | check!("+00:99" , [fix!(TimezoneOffset)]; OUT_OF_RANGE); |
789 | check!("#12:34" , [fix!(TimezoneOffset)]; INVALID); |
790 | check!("12:34" , [fix!(TimezoneOffset)]; INVALID); |
791 | check!("+12:34 " , [fix!(TimezoneOffset)]; TOO_LONG); |
792 | check!(" +12:34" , [fix!(TimezoneOffset)]; offset: 754 * 60); |
793 | check!(" \t -12:34" , [fix!(TimezoneOffset)]; offset: -754 * 60); |
794 | check!("" , [fix!(TimezoneOffset)]; TOO_SHORT); |
795 | check!("+" , [fix!(TimezoneOffset)]; TOO_SHORT); |
796 | check!("+1" , [fix!(TimezoneOffset)]; TOO_SHORT); |
797 | check!("+12" , [fix!(TimezoneOffset)]; TOO_SHORT); |
798 | check!("+123" , [fix!(TimezoneOffset)]; TOO_SHORT); |
799 | check!("+1234" , [fix!(TimezoneOffset)]; offset: 754 * 60); |
800 | check!("+12345" , [fix!(TimezoneOffset)]; TOO_LONG); |
801 | check!("+12345" , [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); |
802 | check!("Z" , [fix!(TimezoneOffset)]; INVALID); |
803 | check!("z" , [fix!(TimezoneOffset)]; INVALID); |
804 | check!("Z" , [fix!(TimezoneOffsetZ)]; offset: 0); |
805 | check!("z" , [fix!(TimezoneOffsetZ)]; offset: 0); |
806 | check!("Y" , [fix!(TimezoneOffsetZ)]; INVALID); |
807 | check!("Zulu" , [fix!(TimezoneOffsetZ), lit!("ulu" )]; offset: 0); |
808 | check!("zulu" , [fix!(TimezoneOffsetZ), lit!("ulu" )]; offset: 0); |
809 | check!("+1234ulu" , [fix!(TimezoneOffsetZ), lit!("ulu" )]; offset: 754 * 60); |
810 | check!("+12:34ulu" , [fix!(TimezoneOffsetZ), lit!("ulu" )]; offset: 754 * 60); |
811 | check!("Z" , [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); |
812 | check!("z" , [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); |
813 | check!("+12:00" , [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); |
814 | check!("+12" , [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); |
815 | check!("CEST 5" , [fix!(TimezoneName), lit!(" " ), num!(Day)]; day: 5); |
816 | |
817 | // some practical examples |
818 | check!("2015-02-04T14:37:05+09:00" , |
819 | [num!(Year), lit!("-" ), num!(Month), lit!("-" ), num!(Day), lit!("T" ), |
820 | num!(Hour), lit!(":" ), num!(Minute), lit!(":" ), num!(Second), fix!(TimezoneOffset)]; |
821 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, |
822 | minute: 37, second: 5, offset: 32400); |
823 | check!("20150204143705567" , |
824 | [num!(Year), num!(Month), num!(Day), |
825 | num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; |
826 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, |
827 | minute: 37, second: 5, nanosecond: 567000000); |
828 | check!("Mon, 10 Jun 2013 09:32:37 GMT" , |
829 | [fix!(ShortWeekdayName), lit!("," ), sp!(" " ), num!(Day), sp!(" " ), |
830 | fix!(ShortMonthName), sp!(" " ), num!(Year), sp!(" " ), num!(Hour), lit!(":" ), |
831 | num!(Minute), lit!(":" ), num!(Second), sp!(" " ), lit!("GMT" )]; |
832 | year: 2013, month: 6, day: 10, weekday: Weekday::Mon, |
833 | hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); |
834 | check!("Sun Aug 02 13:39:15 CEST 2020" , |
835 | [fix!(ShortWeekdayName), sp!(" " ), fix!(ShortMonthName), sp!(" " ), |
836 | num!(Day), sp!(" " ), num!(Hour), lit!(":" ), num!(Minute), lit!(":" ), |
837 | num!(Second), sp!(" " ), fix!(TimezoneName), sp!(" " ), num!(Year)]; |
838 | year: 2020, month: 8, day: 2, weekday: Weekday::Sun, |
839 | hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); |
840 | check!("20060102150405" , |
841 | [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; |
842 | year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); |
843 | check!("3:14PM" , |
844 | [num!(Hour12), lit!(":" ), num!(Minute), fix!(LowerAmPm)]; |
845 | hour_div_12: 1, hour_mod_12: 3, minute: 14); |
846 | check!("12345678901234.56789" , |
847 | [num!(Timestamp), lit!("." ), num!(Nanosecond)]; |
848 | nanosecond: 56_789, timestamp: 12_345_678_901_234); |
849 | check!("12345678901234.56789" , |
850 | [num!(Timestamp), fix!(Nanosecond)]; |
851 | nanosecond: 567_890_000, timestamp: 12_345_678_901_234); |
852 | } |
853 | |
854 | #[cfg (test)] |
855 | #[test ] |
856 | fn test_rfc2822() { |
857 | use super::NOT_ENOUGH; |
858 | use super::*; |
859 | use crate::offset::FixedOffset; |
860 | use crate::DateTime; |
861 | |
862 | // Test data - (input, Ok(expected result after parse and format) or Err(error code)) |
863 | let testdates = [ |
864 | ("Tue, 20 Jan 2015 17:35:20 -0800" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), // normal case |
865 | ("Fri, 2 Jan 2015 17:35:20 -0800" , Ok("Fri, 02 Jan 2015 17:35:20 -0800" )), // folding whitespace |
866 | ("Fri, 02 Jan 2015 17:35:20 -0800" , Ok("Fri, 02 Jan 2015 17:35:20 -0800" )), // leading zero |
867 | ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), // trailing comment |
868 | ( |
869 | r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))" , |
870 | Ok("Tue, 20 Jan 2015 17:35:20 -0800" ), |
871 | ), // complex trailing comment |
872 | (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)" , Err(TOO_LONG)), // incorrect comment, not enough closing parentheses |
873 | ( |
874 | "Tue, 20 Jan 2015 17:35:20 -0800 (UTC) \t \r\n(Anothercomment)" , |
875 | Ok("Tue, 20 Jan 2015 17:35:20 -0800" ), |
876 | ), // multiple comments |
877 | ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) " , Err(TOO_LONG)), // trailing whitespace after comment |
878 | ("20 Jan 2015 17:35:20 -0800" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), // no day of week |
879 | ("20 JAN 2015 17:35:20 -0800" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), // upper case month |
880 | ("Tue, 20 Jan 2015 17:35 -0800" , Ok("Tue, 20 Jan 2015 17:35:00 -0800" )), // no second |
881 | ("11 Sep 2001 09:45:00 EST" , Ok("Tue, 11 Sep 2001 09:45:00 -0500" )), |
882 | ("30 Feb 2015 17:35:20 -0800" , Err(OUT_OF_RANGE)), // bad day of month |
883 | ("Tue, 20 Jan 2015" , Err(TOO_SHORT)), // omitted fields |
884 | ("Tue, 20 Avr 2015 17:35:20 -0800" , Err(INVALID)), // bad month name |
885 | ("Tue, 20 Jan 2015 25:35:20 -0800" , Err(OUT_OF_RANGE)), // bad hour |
886 | ("Tue, 20 Jan 2015 7:35:20 -0800" , Err(INVALID)), // bad # of digits in hour |
887 | ("Tue, 20 Jan 2015 17:65:20 -0800" , Err(OUT_OF_RANGE)), // bad minute |
888 | ("Tue, 20 Jan 2015 17:35:90 -0800" , Err(OUT_OF_RANGE)), // bad second |
889 | ("Tue, 20 Jan 2015 17:35:20 -0890" , Err(OUT_OF_RANGE)), // bad offset |
890 | ("6 Jun 1944 04:00:00Z" , Err(INVALID)), // bad offset (zulu not allowed) |
891 | ("Tue, 20 Jan 2015 17:35:20 HAS" , Err(NOT_ENOUGH)), // bad named time zone |
892 | // named timezones that have specific timezone offsets |
893 | // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 |
894 | ("Tue, 20 Jan 2015 17:35:20 GMT" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
895 | ("Tue, 20 Jan 2015 17:35:20 UT" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
896 | ("Tue, 20 Jan 2015 17:35:20 ut" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
897 | ("Tue, 20 Jan 2015 17:35:20 EDT" , Ok("Tue, 20 Jan 2015 17:35:20 -0400" )), |
898 | ("Tue, 20 Jan 2015 17:35:20 EST" , Ok("Tue, 20 Jan 2015 17:35:20 -0500" )), |
899 | ("Tue, 20 Jan 2015 17:35:20 CDT" , Ok("Tue, 20 Jan 2015 17:35:20 -0500" )), |
900 | ("Tue, 20 Jan 2015 17:35:20 CST" , Ok("Tue, 20 Jan 2015 17:35:20 -0600" )), |
901 | ("Tue, 20 Jan 2015 17:35:20 MDT" , Ok("Tue, 20 Jan 2015 17:35:20 -0600" )), |
902 | ("Tue, 20 Jan 2015 17:35:20 MST" , Ok("Tue, 20 Jan 2015 17:35:20 -0700" )), |
903 | ("Tue, 20 Jan 2015 17:35:20 PDT" , Ok("Tue, 20 Jan 2015 17:35:20 -0700" )), |
904 | ("Tue, 20 Jan 2015 17:35:20 PST" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), |
905 | ("Tue, 20 Jan 2015 17:35:20 pst" , Ok("Tue, 20 Jan 2015 17:35:20 -0800" )), |
906 | // named single-letter military timezones must fallback to +0000 |
907 | ("Tue, 20 Jan 2015 17:35:20 Z" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
908 | ("Tue, 20 Jan 2015 17:35:20 A" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
909 | ("Tue, 20 Jan 2015 17:35:20 a" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
910 | ("Tue, 20 Jan 2015 17:35:20 K" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
911 | ("Tue, 20 Jan 2015 17:35:20 k" , Ok("Tue, 20 Jan 2015 17:35:20 +0000" )), |
912 | // named single-letter timezone "J" is specifically not valid |
913 | ("Tue, 20 Jan 2015 17:35:20 J" , Err(NOT_ENOUGH)), |
914 | ]; |
915 | |
916 | fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> { |
917 | let mut parsed = Parsed::new(); |
918 | parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; |
919 | parsed.to_datetime() |
920 | } |
921 | |
922 | fn fmt_rfc2822_datetime(dt: DateTime<FixedOffset>) -> String { |
923 | dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() |
924 | } |
925 | |
926 | // Test against test data above |
927 | for &(date, checkdate) in testdates.iter() { |
928 | let d = rfc2822_to_datetime(date); // parse a date |
929 | let dt = match d { |
930 | // did we get a value? |
931 | Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on |
932 | Err(e) => Err(e), // otherwise keep an error for the comparison |
933 | }; |
934 | if dt != checkdate.map(|s| s.to_string()) { |
935 | // check for expected result |
936 | panic!( |
937 | "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}" , |
938 | date, dt, checkdate |
939 | ); |
940 | } |
941 | } |
942 | } |
943 | |
944 | #[cfg (test)] |
945 | #[test ] |
946 | fn parse_rfc850() { |
947 | use crate::{TimeZone, Utc}; |
948 | |
949 | static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT" ; |
950 | |
951 | let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT" ; |
952 | let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); |
953 | |
954 | // Check that the format is what we expect |
955 | assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); |
956 | |
957 | // Check that it parses correctly |
958 | assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT" , RFC850_FMT)); |
959 | |
960 | // Check that the rest of the weekdays parse correctly (this test originally failed because |
961 | // Sunday parsed incorrectly). |
962 | let testdates = [ |
963 | (Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), "Monday, 07-Nov-94 08:49:37 GMT" ), |
964 | (Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), "Tuesday, 08-Nov-94 08:49:37 GMT" ), |
965 | ( |
966 | Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), |
967 | "Wednesday, 09-Nov-94 08:49:37 GMT" , |
968 | ), |
969 | ( |
970 | Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), |
971 | "Thursday, 10-Nov-94 08:49:37 GMT" , |
972 | ), |
973 | (Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), "Friday, 11-Nov-94 08:49:37 GMT" ), |
974 | ( |
975 | Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), |
976 | "Saturday, 12-Nov-94 08:49:37 GMT" , |
977 | ), |
978 | ]; |
979 | |
980 | for val in &testdates { |
981 | assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); |
982 | } |
983 | } |
984 | |
985 | #[cfg (test)] |
986 | #[test ] |
987 | fn test_rfc3339() { |
988 | use super::*; |
989 | use crate::offset::FixedOffset; |
990 | use crate::DateTime; |
991 | |
992 | // Test data - (input, Ok(expected result after parse and format) or Err(error code)) |
993 | let testdates = [ |
994 | ("2015-01-20T17:35:20-08:00" , Ok("2015-01-20T17:35:20-08:00" )), // normal case |
995 | ("1944-06-06T04:04:00Z" , Ok("1944-06-06T04:04:00+00:00" )), // D-day |
996 | ("2001-09-11T09:45:00-08:00" , Ok("2001-09-11T09:45:00-08:00" )), |
997 | ("2015-01-20T17:35:20.001-08:00" , Ok("2015-01-20T17:35:20.001-08:00" )), |
998 | ("2015-01-20T17:35:20.000031-08:00" , Ok("2015-01-20T17:35:20.000031-08:00" )), |
999 | ("2015-01-20T17:35:20.000000004-08:00" , Ok("2015-01-20T17:35:20.000000004-08:00" )), |
1000 | ("2015-01-20T17:35:20.000000000452-08:00" , Ok("2015-01-20T17:35:20-08:00" )), // too small |
1001 | ("2015-02-30T17:35:20-08:00" , Err(OUT_OF_RANGE)), // bad day of month |
1002 | ("2015-01-20T25:35:20-08:00" , Err(OUT_OF_RANGE)), // bad hour |
1003 | ("2015-01-20T17:65:20-08:00" , Err(OUT_OF_RANGE)), // bad minute |
1004 | ("2015-01-20T17:35:90-08:00" , Err(OUT_OF_RANGE)), // bad second |
1005 | ("2015-01-20T17:35:20-24:00" , Err(OUT_OF_RANGE)), // bad offset |
1006 | ]; |
1007 | |
1008 | fn rfc3339_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> { |
1009 | let mut parsed = Parsed::new(); |
1010 | parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; |
1011 | parsed.to_datetime() |
1012 | } |
1013 | |
1014 | fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String { |
1015 | dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() |
1016 | } |
1017 | |
1018 | // Test against test data above |
1019 | for &(date, checkdate) in testdates.iter() { |
1020 | let d = rfc3339_to_datetime(date); // parse a date |
1021 | let dt = match d { |
1022 | // did we get a value? |
1023 | Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on |
1024 | Err(e) => Err(e), // otherwise keep an error for the comparison |
1025 | }; |
1026 | if dt != checkdate.map(|s| s.to_string()) { |
1027 | // check for expected result |
1028 | panic!( |
1029 | "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}" , |
1030 | date, dt, checkdate |
1031 | ); |
1032 | } |
1033 | } |
1034 | } |
1035 | |
1036 | #[cfg (test)] |
1037 | #[test ] |
1038 | fn test_issue_1010() { |
1039 | let dt = crate::NaiveDateTime::parse_from_str(" \u{c}SUN \u{e}\u{3000}\0m@J \u{3000}\0\u{3000}\0m \u{c}! \u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A \u{c}\u{b}\0SU \u{c}\u{c}" , |
1040 | " \u{c}\u{c}%A \u{c}\u{b}\0SUN \u{c}\u{c}\u{c}SUNN \u{c}\u{c}\u{c}SUN \u{c}\u{c}! \u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A \u{c}\u{b}%a" ); |
1041 | assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); |
1042 | } |
1043 | |