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 | use core::borrow::Borrow; |
8 | use core::str; |
9 | |
10 | use super::scan; |
11 | use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; |
12 | use super::{ParseError, ParseResult}; |
13 | use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; |
14 | use crate::{DateTime, FixedOffset, Weekday}; |
15 | |
16 | fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { |
17 | p.set_weekday(match v { |
18 | 0 => Weekday::Sun, |
19 | 1 => Weekday::Mon, |
20 | 2 => Weekday::Tue, |
21 | 3 => Weekday::Wed, |
22 | 4 => Weekday::Thu, |
23 | 5 => Weekday::Fri, |
24 | 6 => Weekday::Sat, |
25 | _ => return Err(OUT_OF_RANGE), |
26 | }) |
27 | } |
28 | |
29 | fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<()> { |
30 | p.set_weekday(match v { |
31 | 1 => Weekday::Mon, |
32 | 2 => Weekday::Tue, |
33 | 3 => Weekday::Wed, |
34 | 4 => Weekday::Thu, |
35 | 5 => Weekday::Fri, |
36 | 6 => Weekday::Sat, |
37 | 7 => Weekday::Sun, |
38 | _ => return Err(OUT_OF_RANGE), |
39 | }) |
40 | } |
41 | |
42 | fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { |
43 | macro_rules! try_consume { |
44 | ($e:expr) => {{ |
45 | let (s_, v) = $e?; |
46 | s = s_; |
47 | v |
48 | }}; |
49 | } |
50 | |
51 | // an adapted RFC 2822 syntax from Section 3.3 and 4.3: |
52 | // |
53 | // c-char = <any char except '(', ')' and '\\'> |
54 | // c-escape = "\" <any char> |
55 | // comment = "(" *(comment / c-char / c-escape) ")" *S |
56 | // date-time = [ day-of-week "," ] date 1*S time *S *comment |
57 | // day-of-week = *S day-name *S |
58 | // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" |
59 | // date = day month year |
60 | // day = *S 1*2DIGIT *S |
61 | // month = 1*S month-name 1*S |
62 | // month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / |
63 | // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" |
64 | // year = *S 2*DIGIT *S |
65 | // time = time-of-day 1*S zone |
66 | // time-of-day = hour ":" minute [ ":" second ] |
67 | // hour = *S 2DIGIT *S |
68 | // minute = *S 2DIGIT *S |
69 | // second = *S 2DIGIT *S |
70 | // zone = ( "+" / "-" ) 4DIGIT / |
71 | // "UT" / "GMT" / ; same as +0000 |
72 | // "EST" / "CST" / "MST" / "PST" / ; same as -0500 to -0800 |
73 | // "EDT" / "CDT" / "MDT" / "PDT" / ; same as -0400 to -0700 |
74 | // 1*(%d65-90 / %d97-122) ; same as -0000 |
75 | // |
76 | // some notes: |
77 | // |
78 | // - quoted characters can be in any mixture of lower and upper cases. |
79 | // |
80 | // - we do not recognize a folding white space (FWS) or comment (CFWS). |
81 | // for our purposes, instead, we accept any sequence of Unicode |
82 | // white space characters (denoted here to `S`). For comments, we accept |
83 | // any text within parentheses while respecting escaped parentheses. |
84 | // Any actual RFC 2822 parser is expected to parse FWS and/or CFWS themselves |
85 | // and replace it with a single SP (`%x20`); this is legitimate. |
86 | // |
87 | // - two-digit year < 50 should be interpreted by adding 2000. |
88 | // two-digit year >= 50 or three-digit year should be interpreted |
89 | // by adding 1900. note that four-or-more-digit years less than 1000 |
90 | // are *never* affected by this rule. |
91 | // |
92 | // - mismatching day-of-week is always an error, which is consistent to |
93 | // Chrono's own rules. |
94 | // |
95 | // - zones can range from `-9959` to `+9959`, but `FixedOffset` does not |
96 | // support offsets larger than 24 hours. this is not *that* problematic |
97 | // since we do not directly go to a `DateTime` so one can recover |
98 | // the offset information from `Parsed` anyway. |
99 | |
100 | s = s.trim_start(); |
101 | |
102 | if let Ok((s_, weekday)) = scan::short_weekday(s) { |
103 | if !s_.starts_with(',' ) { |
104 | return Err(INVALID); |
105 | } |
106 | s = &s_[1..]; |
107 | parsed.set_weekday(weekday)?; |
108 | } |
109 | |
110 | s = s.trim_start(); |
111 | parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; |
112 | s = scan::space(s)?; // mandatory |
113 | parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; |
114 | s = scan::space(s)?; // mandatory |
115 | |
116 | // distinguish two- and three-digit years from four-digit years |
117 | let prevlen = s.len(); |
118 | let mut year = try_consume!(scan::number(s, 2, usize::MAX)); |
119 | let yearlen = prevlen - s.len(); |
120 | match (yearlen, year) { |
121 | (2, 0..=49) => { |
122 | year += 2000; |
123 | } // 47 -> 2047, 05 -> 2005 |
124 | (2, 50..=99) => { |
125 | year += 1900; |
126 | } // 79 -> 1979 |
127 | (3, _) => { |
128 | year += 1900; |
129 | } // 112 -> 2012, 009 -> 1909 |
130 | (_, _) => {} // 1987 -> 1987, 0654 -> 0654 |
131 | } |
132 | parsed.set_year(year)?; |
133 | |
134 | s = scan::space(s)?; // mandatory |
135 | parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; |
136 | s = scan::char(s.trim_start(), b':' )?.trim_start(); // *S ":" *S |
137 | parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; |
138 | if let Ok(s_) = scan::char(s.trim_start(), b':' ) { |
139 | // [ ":" *S 2DIGIT ] |
140 | parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; |
141 | } |
142 | |
143 | s = scan::space(s)?; // mandatory |
144 | parsed.set_offset(i64::from(try_consume!(scan::timezone_offset_2822(s))))?; |
145 | |
146 | // optional comments |
147 | while let Ok((s_out, ())) = scan::comment_2822(s) { |
148 | s = s_out; |
149 | } |
150 | |
151 | Ok((s, ())) |
152 | } |
153 | |
154 | pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { |
155 | macro_rules! try_consume { |
156 | ($e:expr) => {{ |
157 | let (s_, v) = $e?; |
158 | s = s_; |
159 | v |
160 | }}; |
161 | } |
162 | |
163 | // an adapted RFC 3339 syntax from Section 5.6: |
164 | // |
165 | // date-fullyear = 4DIGIT |
166 | // date-month = 2DIGIT ; 01-12 |
167 | // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year |
168 | // time-hour = 2DIGIT ; 00-23 |
169 | // time-minute = 2DIGIT ; 00-59 |
170 | // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules |
171 | // time-secfrac = "." 1*DIGIT |
172 | // time-numoffset = ("+" / "-") time-hour ":" time-minute |
173 | // time-offset = "Z" / time-numoffset |
174 | // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] |
175 | // full-date = date-fullyear "-" date-month "-" date-mday |
176 | // full-time = partial-time time-offset |
177 | // date-time = full-date "T" full-time |
178 | // |
179 | // some notes: |
180 | // |
181 | // - quoted characters can be in any mixture of lower and upper cases. |
182 | // |
183 | // - it may accept any number of fractional digits for seconds. |
184 | // for Chrono, this means that we should skip digits past first 9 digits. |
185 | // |
186 | // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. |
187 | // note that this restriction is unique to RFC 3339 and not ISO 8601. |
188 | // since this is not a typical Chrono behavior, we check it earlier. |
189 | // |
190 | // - For readability a full-date and a full-time may be separated by a space character. |
191 | |
192 | parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; |
193 | s = scan::char(s, b'-' )?; |
194 | parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; |
195 | s = scan::char(s, b'-' )?; |
196 | parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; |
197 | |
198 | s = match s.as_bytes().first() { |
199 | Some(&b't' | &b'T' | &b' ' ) => &s[1..], |
200 | Some(_) => return Err(INVALID), |
201 | None => return Err(TOO_SHORT), |
202 | }; |
203 | |
204 | parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; |
205 | s = scan::char(s, b':' )?; |
206 | parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; |
207 | s = scan::char(s, b':' )?; |
208 | parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; |
209 | if s.starts_with('.' ) { |
210 | let nanosecond = try_consume!(scan::nanosecond(&s[1..])); |
211 | parsed.set_nanosecond(nanosecond)?; |
212 | } |
213 | |
214 | let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':' ), true, false, true)); |
215 | // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant. |
216 | // But it is possible to read the offset directly from `Parsed`. We want to only successfully |
217 | // populate `Parsed` if the input is fully valid RFC 3339. |
218 | // Max for the hours field is `23`, and for the minutes field `59`. |
219 | const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60; |
220 | if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { |
221 | return Err(OUT_OF_RANGE); |
222 | } |
223 | parsed.set_offset(i64::from(offset))?; |
224 | |
225 | Ok((s, ())) |
226 | } |
227 | |
228 | /// Tries to parse given string into `parsed` with given formatting items. |
229 | /// Returns `Ok` when the entire string has been parsed (otherwise `parsed` should not be used). |
230 | /// There should be no trailing string after parsing; |
231 | /// use a stray [`Item::Space`](./enum.Item.html#variant.Space) to trim whitespaces. |
232 | /// |
233 | /// This particular date and time parser is: |
234 | /// |
235 | /// - Greedy. It will consume the longest possible prefix. |
236 | /// For example, `April` is always consumed entirely when the long month name is requested; |
237 | /// it equally accepts `Apr`, but prefers the longer prefix in this case. |
238 | /// |
239 | /// - Padding-agnostic (for numeric items). |
240 | /// The [`Pad`](./enum.Pad.html) field is completely ignored, |
241 | /// so one can prepend any number of whitespace then any number of zeroes before numbers. |
242 | /// |
243 | /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. |
244 | pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> |
245 | where |
246 | I: Iterator<Item = B>, |
247 | B: Borrow<Item<'a>>, |
248 | { |
249 | match parse_internal(parsed, s, items) { |
250 | Ok("" ) => Ok(()), |
251 | Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error |
252 | Err(e: ParseError) => Err(e), |
253 | } |
254 | } |
255 | |
256 | /// Tries to parse given string into `parsed` with given formatting items. |
257 | /// Returns `Ok` with a slice of the unparsed remainder. |
258 | /// |
259 | /// This particular date and time parser is: |
260 | /// |
261 | /// - Greedy. It will consume the longest possible prefix. |
262 | /// For example, `April` is always consumed entirely when the long month name is requested; |
263 | /// it equally accepts `Apr`, but prefers the longer prefix in this case. |
264 | /// |
265 | /// - Padding-agnostic (for numeric items). |
266 | /// The [`Pad`](./enum.Pad.html) field is completely ignored, |
267 | /// so one can prepend any number of zeroes before numbers. |
268 | /// |
269 | /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. |
270 | pub fn parse_and_remainder<'a, 'b, I, B>( |
271 | parsed: &mut Parsed, |
272 | s: &'b str, |
273 | items: I, |
274 | ) -> ParseResult<&'b str> |
275 | where |
276 | I: Iterator<Item = B>, |
277 | B: Borrow<Item<'a>>, |
278 | { |
279 | parse_internal(parsed, s, items) |
280 | } |
281 | |
282 | fn parse_internal<'a, 'b, I, B>( |
283 | parsed: &mut Parsed, |
284 | mut s: &'b str, |
285 | items: I, |
286 | ) -> Result<&'b str, ParseError> |
287 | where |
288 | I: Iterator<Item = B>, |
289 | B: Borrow<Item<'a>>, |
290 | { |
291 | macro_rules! try_consume { |
292 | ($e:expr) => {{ |
293 | match $e { |
294 | Ok((s_, v)) => { |
295 | s = s_; |
296 | v |
297 | } |
298 | Err(e) => return Err(e), |
299 | } |
300 | }}; |
301 | } |
302 | |
303 | for item in items { |
304 | match *item.borrow() { |
305 | Item::Literal(prefix) => { |
306 | if s.len() < prefix.len() { |
307 | return Err(TOO_SHORT); |
308 | } |
309 | if !s.starts_with(prefix) { |
310 | return Err(INVALID); |
311 | } |
312 | s = &s[prefix.len()..]; |
313 | } |
314 | |
315 | #[cfg (feature = "alloc" )] |
316 | Item::OwnedLiteral(ref prefix) => { |
317 | if s.len() < prefix.len() { |
318 | return Err(TOO_SHORT); |
319 | } |
320 | if !s.starts_with(&prefix[..]) { |
321 | return Err(INVALID); |
322 | } |
323 | s = &s[prefix.len()..]; |
324 | } |
325 | |
326 | Item::Space(_) => { |
327 | s = s.trim_start(); |
328 | } |
329 | |
330 | #[cfg (feature = "alloc" )] |
331 | Item::OwnedSpace(_) => { |
332 | s = s.trim_start(); |
333 | } |
334 | |
335 | Item::Numeric(ref spec, ref _pad) => { |
336 | use super::Numeric::*; |
337 | type Setter = fn(&mut Parsed, i64) -> ParseResult<()>; |
338 | |
339 | let (width, signed, set): (usize, bool, Setter) = match *spec { |
340 | Year => (4, true, Parsed::set_year), |
341 | YearDiv100 => (2, false, Parsed::set_year_div_100), |
342 | YearMod100 => (2, false, Parsed::set_year_mod_100), |
343 | IsoYear => (4, true, Parsed::set_isoyear), |
344 | IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), |
345 | IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), |
346 | Month => (2, false, Parsed::set_month), |
347 | Day => (2, false, Parsed::set_day), |
348 | WeekFromSun => (2, false, Parsed::set_week_from_sun), |
349 | WeekFromMon => (2, false, Parsed::set_week_from_mon), |
350 | IsoWeek => (2, false, Parsed::set_isoweek), |
351 | NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday), |
352 | WeekdayFromMon => (1, false, set_weekday_with_number_from_monday), |
353 | Ordinal => (3, false, Parsed::set_ordinal), |
354 | Hour => (2, false, Parsed::set_hour), |
355 | Hour12 => (2, false, Parsed::set_hour12), |
356 | Minute => (2, false, Parsed::set_minute), |
357 | Second => (2, false, Parsed::set_second), |
358 | Nanosecond => (9, false, Parsed::set_nanosecond), |
359 | Timestamp => (usize::MAX, false, Parsed::set_timestamp), |
360 | |
361 | // for the future expansion |
362 | Internal(ref int) => match int._dummy {}, |
363 | }; |
364 | |
365 | s = s.trim_start(); |
366 | let v = if signed { |
367 | if s.starts_with('-' ) { |
368 | let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); |
369 | 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? |
370 | } else if s.starts_with('+' ) { |
371 | try_consume!(scan::number(&s[1..], 1, usize::MAX)) |
372 | } else { |
373 | // if there is no explicit sign, we respect the original `width` |
374 | try_consume!(scan::number(s, 1, width)) |
375 | } |
376 | } else { |
377 | try_consume!(scan::number(s, 1, width)) |
378 | }; |
379 | set(parsed, v)?; |
380 | } |
381 | |
382 | Item::Fixed(ref spec) => { |
383 | use super::Fixed::*; |
384 | |
385 | match spec { |
386 | &ShortMonthName => { |
387 | let month0 = try_consume!(scan::short_month0(s)); |
388 | parsed.set_month(i64::from(month0) + 1)?; |
389 | } |
390 | |
391 | &LongMonthName => { |
392 | let month0 = try_consume!(scan::short_or_long_month0(s)); |
393 | parsed.set_month(i64::from(month0) + 1)?; |
394 | } |
395 | |
396 | &ShortWeekdayName => { |
397 | let weekday = try_consume!(scan::short_weekday(s)); |
398 | parsed.set_weekday(weekday)?; |
399 | } |
400 | |
401 | &LongWeekdayName => { |
402 | let weekday = try_consume!(scan::short_or_long_weekday(s)); |
403 | parsed.set_weekday(weekday)?; |
404 | } |
405 | |
406 | &LowerAmPm | &UpperAmPm => { |
407 | if s.len() < 2 { |
408 | return Err(TOO_SHORT); |
409 | } |
410 | let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { |
411 | (b'a' , b'm' ) => false, |
412 | (b'p' , b'm' ) => true, |
413 | _ => return Err(INVALID), |
414 | }; |
415 | parsed.set_ampm(ampm)?; |
416 | s = &s[2..]; |
417 | } |
418 | |
419 | &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { |
420 | if s.starts_with('.' ) { |
421 | let nano = try_consume!(scan::nanosecond(&s[1..])); |
422 | parsed.set_nanosecond(nano)?; |
423 | } |
424 | } |
425 | |
426 | &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { |
427 | if s.len() < 3 { |
428 | return Err(TOO_SHORT); |
429 | } |
430 | let nano = try_consume!(scan::nanosecond_fixed(s, 3)); |
431 | parsed.set_nanosecond(nano)?; |
432 | } |
433 | |
434 | &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { |
435 | if s.len() < 6 { |
436 | return Err(TOO_SHORT); |
437 | } |
438 | let nano = try_consume!(scan::nanosecond_fixed(s, 6)); |
439 | parsed.set_nanosecond(nano)?; |
440 | } |
441 | |
442 | &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { |
443 | if s.len() < 9 { |
444 | return Err(TOO_SHORT); |
445 | } |
446 | let nano = try_consume!(scan::nanosecond_fixed(s, 9)); |
447 | parsed.set_nanosecond(nano)?; |
448 | } |
449 | |
450 | &TimezoneName => { |
451 | try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); |
452 | } |
453 | |
454 | &TimezoneOffsetColon |
455 | | &TimezoneOffsetDoubleColon |
456 | | &TimezoneOffsetTripleColon |
457 | | &TimezoneOffset => { |
458 | let offset = try_consume!(scan::timezone_offset( |
459 | s.trim_start(), |
460 | scan::colon_or_space, |
461 | false, |
462 | false, |
463 | true, |
464 | )); |
465 | parsed.set_offset(i64::from(offset))?; |
466 | } |
467 | |
468 | &TimezoneOffsetColonZ | &TimezoneOffsetZ => { |
469 | let offset = try_consume!(scan::timezone_offset( |
470 | s.trim_start(), |
471 | scan::colon_or_space, |
472 | true, |
473 | false, |
474 | true, |
475 | )); |
476 | parsed.set_offset(i64::from(offset))?; |
477 | } |
478 | &Internal(InternalFixed { |
479 | val: InternalInternal::TimezoneOffsetPermissive, |
480 | }) => { |
481 | let offset = try_consume!(scan::timezone_offset( |
482 | s.trim_start(), |
483 | scan::colon_or_space, |
484 | true, |
485 | true, |
486 | true, |
487 | )); |
488 | parsed.set_offset(i64::from(offset))?; |
489 | } |
490 | |
491 | &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), |
492 | &RFC3339 => { |
493 | // Used for the `%+` specifier, which has the description: |
494 | // "Same as `%Y-%m-%dT%H:%M:%S%.f%:z` (...) |
495 | // This format also supports having a `Z` or `UTC` in place of `%:z`." |
496 | // Use the relaxed parser to match this description. |
497 | try_consume!(parse_rfc3339_relaxed(parsed, s)) |
498 | } |
499 | } |
500 | } |
501 | |
502 | Item::Error => { |
503 | return Err(BAD_FORMAT); |
504 | } |
505 | } |
506 | } |
507 | Ok(s) |
508 | } |
509 | |
510 | /// Accepts a relaxed form of RFC3339. |
511 | /// A space or a 'T' are accepted as the separator between the date and time |
512 | /// parts. Additional spaces are allowed between each component. |
513 | /// |
514 | /// All of these examples are equivalent: |
515 | /// ``` |
516 | /// # use chrono::{DateTime, offset::FixedOffset}; |
517 | /// "2012-12-12T12:12:12Z" .parse::<DateTime<FixedOffset>>()?; |
518 | /// "2012-12-12 12:12:12Z" .parse::<DateTime<FixedOffset>>()?; |
519 | /// "2012- 12-12T12: 12:12Z" .parse::<DateTime<FixedOffset>>()?; |
520 | /// # Ok::<(), chrono::ParseError>(()) |
521 | /// ``` |
522 | impl str::FromStr for DateTime<FixedOffset> { |
523 | type Err = ParseError; |
524 | |
525 | fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> { |
526 | let mut parsed: Parsed = Parsed::new(); |
527 | let (s: &str, _) = parse_rfc3339_relaxed(&mut parsed, s)?; |
528 | if !s.trim_start().is_empty() { |
529 | return Err(TOO_LONG); |
530 | } |
531 | parsed.to_datetime() |
532 | } |
533 | } |
534 | |
535 | /// Accepts a relaxed form of RFC3339. |
536 | /// |
537 | /// Differences with RFC3339: |
538 | /// - Values don't require padding to two digits. |
539 | /// - Years outside the range 0...=9999 are accepted, but they must include a sign. |
540 | /// - `UTC` is accepted as a valid timezone name/offset (for compatibility with the debug format of |
541 | /// `DateTime<Utc>`. |
542 | /// - There can be spaces between any of the components. |
543 | /// - The colon in the offset may be missing. |
544 | fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { |
545 | const DATE_ITEMS: &[Item<'static>] = &[ |
546 | Item::Numeric(Numeric::Year, Pad::Zero), |
547 | Item::Space("" ), |
548 | Item::Literal("-" ), |
549 | Item::Numeric(Numeric::Month, Pad::Zero), |
550 | Item::Space("" ), |
551 | Item::Literal("-" ), |
552 | Item::Numeric(Numeric::Day, Pad::Zero), |
553 | ]; |
554 | const TIME_ITEMS: &[Item<'static>] = &[ |
555 | Item::Numeric(Numeric::Hour, Pad::Zero), |
556 | Item::Space("" ), |
557 | Item::Literal(":" ), |
558 | Item::Numeric(Numeric::Minute, Pad::Zero), |
559 | Item::Space("" ), |
560 | Item::Literal(":" ), |
561 | Item::Numeric(Numeric::Second, Pad::Zero), |
562 | Item::Fixed(Fixed::Nanosecond), |
563 | Item::Space("" ), |
564 | ]; |
565 | |
566 | s = parse_internal(parsed, s, DATE_ITEMS.iter())?; |
567 | |
568 | s = match s.as_bytes().first() { |
569 | Some(&b't' | &b'T' | &b' ' ) => &s[1..], |
570 | Some(_) => return Err(INVALID), |
571 | None => return Err(TOO_SHORT), |
572 | }; |
573 | |
574 | s = parse_internal(parsed, s, TIME_ITEMS.iter())?; |
575 | s = s.trim_start(); |
576 | let (s, offset) = if s.len() >= 3 && "UTC" .as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { |
577 | (&s[3..], 0) |
578 | } else { |
579 | scan::timezone_offset(s, scan::colon_or_space, true, false, true)? |
580 | }; |
581 | parsed.set_offset(i64::from(offset))?; |
582 | Ok((s, ())) |
583 | } |
584 | |
585 | #[cfg (test)] |
586 | mod tests { |
587 | use crate::format::*; |
588 | use crate::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Timelike, Utc}; |
589 | |
590 | macro_rules! parsed { |
591 | ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { |
592 | let mut expected = Parsed::new(); |
593 | $(expected.$k = Some($v);)* |
594 | Ok(expected) |
595 | }); |
596 | } |
597 | |
598 | #[test ] |
599 | fn test_parse_whitespace_and_literal() { |
600 | use crate::format::Item::{Literal, Space}; |
601 | |
602 | // empty string |
603 | parses("" , &[]); |
604 | check(" " , &[], Err(TOO_LONG)); |
605 | check("a" , &[], Err(TOO_LONG)); |
606 | check("abc" , &[], Err(TOO_LONG)); |
607 | check("🤠" , &[], Err(TOO_LONG)); |
608 | |
609 | // whitespaces |
610 | parses("" , &[Space("" )]); |
611 | parses(" " , &[Space(" " )]); |
612 | parses(" " , &[Space(" " )]); |
613 | parses(" " , &[Space(" " )]); |
614 | parses(" " , &[Space("" )]); |
615 | parses(" " , &[Space(" " )]); |
616 | parses(" " , &[Space(" " )]); |
617 | parses(" " , &[Space(" " )]); |
618 | parses("" , &[Space(" " )]); |
619 | parses(" " , &[Space(" " )]); |
620 | parses(" " , &[Space(" " )]); |
621 | parses(" " , &[Space(" " ), Space(" " )]); |
622 | parses(" " , &[Space(" " ), Space(" " )]); |
623 | parses(" " , &[Space(" " ), Space(" " )]); |
624 | parses(" " , &[Space(" " ), Space(" " )]); |
625 | parses(" " , &[Space(" " ), Space(" " )]); |
626 | parses(" " , &[Space(" " ), Space(" " ), Space(" " )]); |
627 | parses(" \t" , &[Space("" )]); |
628 | parses(" \n\r \n" , &[Space("" )]); |
629 | parses(" \t" , &[Space(" \t" )]); |
630 | parses(" \t" , &[Space(" " )]); |
631 | parses(" " , &[Space(" \t" )]); |
632 | parses(" \t\r" , &[Space(" \t\r" )]); |
633 | parses(" \t\r " , &[Space(" \t\r " )]); |
634 | parses(" \t \r" , &[Space(" \t \r" )]); |
635 | parses(" \t\r" , &[Space(" \t\r" )]); |
636 | parses(" \n\r \n" , &[Space(" \n\r \n" )]); |
637 | parses(" \t\n" , &[Space(" \t" )]); |
638 | parses(" \n\t" , &[Space(" \t\n" )]); |
639 | parses(" \u{2002}" , &[Space(" \u{2002}" )]); |
640 | // most unicode whitespace characters |
641 | parses( |
642 | " \u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}" , |
643 | &[Space(" \u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}" )] |
644 | ); |
645 | // most unicode whitespace characters |
646 | parses( |
647 | " \u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}" , |
648 | &[ |
649 | Space(" \u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}" ), |
650 | Space(" \u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}" ) |
651 | ] |
652 | ); |
653 | check("a" , &[Space("" )], Err(TOO_LONG)); |
654 | check("a" , &[Space(" " )], Err(TOO_LONG)); |
655 | // a Space containing a literal does not match a literal |
656 | check("a" , &[Space("a" )], Err(TOO_LONG)); |
657 | check("abc" , &[Space("" )], Err(TOO_LONG)); |
658 | check("abc" , &[Space(" " )], Err(TOO_LONG)); |
659 | check(" abc" , &[Space("" )], Err(TOO_LONG)); |
660 | check(" abc" , &[Space(" " )], Err(TOO_LONG)); |
661 | |
662 | // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" |
663 | |
664 | // literal |
665 | parses("" , &[Literal("" )]); |
666 | check("" , &[Literal("a" )], Err(TOO_SHORT)); |
667 | check(" " , &[Literal("a" )], Err(INVALID)); |
668 | parses("a" , &[Literal("a" )]); |
669 | parses("+" , &[Literal("+" )]); |
670 | parses("-" , &[Literal("-" )]); |
671 | parses("−" , &[Literal("−" )]); // MINUS SIGN (U+2212) |
672 | parses(" " , &[Literal(" " )]); // a Literal may contain whitespace and match whitespace |
673 | check("aa" , &[Literal("a" )], Err(TOO_LONG)); |
674 | check("🤠" , &[Literal("a" )], Err(INVALID)); |
675 | check("A" , &[Literal("a" )], Err(INVALID)); |
676 | check("a" , &[Literal("z" )], Err(INVALID)); |
677 | check("a" , &[Literal("🤠" )], Err(TOO_SHORT)); |
678 | check("a" , &[Literal(" \u{0363}a" )], Err(TOO_SHORT)); |
679 | check(" \u{0363}a" , &[Literal("a" )], Err(INVALID)); |
680 | parses(" \u{0363}a" , &[Literal(" \u{0363}a" )]); |
681 | check("a" , &[Literal("ab" )], Err(TOO_SHORT)); |
682 | parses("xy" , &[Literal("xy" )]); |
683 | parses("xy" , &[Literal("x" ), Literal("y" )]); |
684 | parses("1" , &[Literal("1" )]); |
685 | parses("1234" , &[Literal("1234" )]); |
686 | parses("+1234" , &[Literal("+1234" )]); |
687 | parses("-1234" , &[Literal("-1234" )]); |
688 | parses("−1234" , &[Literal("−1234" )]); // MINUS SIGN (U+2212) |
689 | parses("PST" , &[Literal("PST" )]); |
690 | parses("🤠" , &[Literal("🤠" )]); |
691 | parses("🤠a" , &[Literal("🤠" ), Literal("a" )]); |
692 | parses("🤠a🤠" , &[Literal("🤠" ), Literal("a🤠" )]); |
693 | parses("a🤠b" , &[Literal("a" ), Literal("🤠" ), Literal("b" )]); |
694 | // literals can be together |
695 | parses("xy" , &[Literal("xy" )]); |
696 | parses("xyz" , &[Literal("xyz" )]); |
697 | // or literals can be apart |
698 | parses("xy" , &[Literal("x" ), Literal("y" )]); |
699 | parses("xyz" , &[Literal("x" ), Literal("yz" )]); |
700 | parses("xyz" , &[Literal("xy" ), Literal("z" )]); |
701 | parses("xyz" , &[Literal("x" ), Literal("y" ), Literal("z" )]); |
702 | // |
703 | check("x y" , &[Literal("x" ), Literal("y" )], Err(INVALID)); |
704 | parses("xy" , &[Literal("x" ), Space("" ), Literal("y" )]); |
705 | parses("x y" , &[Literal("x" ), Space("" ), Literal("y" )]); |
706 | parses("x y" , &[Literal("x" ), Space(" " ), Literal("y" )]); |
707 | |
708 | // whitespaces + literals |
709 | parses("a \n" , &[Literal("a" ), Space(" \n" )]); |
710 | parses(" \tab \n" , &[Space(" \t" ), Literal("ab" ), Space(" \n" )]); |
711 | parses( |
712 | "ab \tcd \ne" , |
713 | &[Literal("ab" ), Space(" \t" ), Literal("cd" ), Space(" \n" ), Literal("e" )], |
714 | ); |
715 | parses( |
716 | "+1ab \tcd \r\n+,." , |
717 | &[Literal("+1ab" ), Space(" \t" ), Literal("cd" ), Space(" \r\n" ), Literal("+,." )], |
718 | ); |
719 | // whitespace and literals can be intermixed |
720 | parses("a \tb" , &[Literal("a \tb" )]); |
721 | parses("a \tb" , &[Literal("a" ), Space(" \t" ), Literal("b" )]); |
722 | } |
723 | |
724 | #[test ] |
725 | fn test_parse_numeric() { |
726 | use crate::format::Item::{Literal, Space}; |
727 | use crate::format::Numeric::*; |
728 | |
729 | // numeric |
730 | check("1987" , &[num(Year)], parsed!(year: 1987)); |
731 | check("1987 " , &[num(Year)], Err(TOO_LONG)); |
732 | check("0x12" , &[num(Year)], Err(TOO_LONG)); // `0` is parsed |
733 | check("x123" , &[num(Year)], Err(INVALID)); |
734 | check("o123" , &[num(Year)], Err(INVALID)); |
735 | check("2015" , &[num(Year)], parsed!(year: 2015)); |
736 | check("0000" , &[num(Year)], parsed!(year: 0)); |
737 | check("9999" , &[num(Year)], parsed!(year: 9999)); |
738 | check(" \t987" , &[num(Year)], parsed!(year: 987)); |
739 | check(" \t987" , &[Space(" \t" ), num(Year)], parsed!(year: 987)); |
740 | check(" \t987🤠" , &[Space(" \t" ), num(Year), Literal("🤠" )], parsed!(year: 987)); |
741 | check("987🤠" , &[num(Year), Literal("🤠" )], parsed!(year: 987)); |
742 | check("5" , &[num(Year)], parsed!(year: 5)); |
743 | check("5 \0" , &[num(Year)], Err(TOO_LONG)); |
744 | check(" \x005" , &[num(Year)], Err(INVALID)); |
745 | check("" , &[num(Year)], Err(TOO_SHORT)); |
746 | check("12345" , &[num(Year), Literal("5" )], parsed!(year: 1234)); |
747 | check("12345" , &[nums(Year), Literal("5" )], parsed!(year: 1234)); |
748 | check("12345" , &[num0(Year), Literal("5" )], parsed!(year: 1234)); |
749 | check("12341234" , &[num(Year), num(Year)], parsed!(year: 1234)); |
750 | check("1234 1234" , &[num(Year), num(Year)], parsed!(year: 1234)); |
751 | check("1234 1234" , &[num(Year), Space(" " ), num(Year)], parsed!(year: 1234)); |
752 | check("1234 1235" , &[num(Year), num(Year)], Err(IMPOSSIBLE)); |
753 | check("1234 1234" , &[num(Year), Literal("x" ), num(Year)], Err(INVALID)); |
754 | check("1234x1234" , &[num(Year), Literal("x" ), num(Year)], parsed!(year: 1234)); |
755 | check("1234 x 1234" , &[num(Year), Literal("x" ), num(Year)], Err(INVALID)); |
756 | check("1234xx1234" , &[num(Year), Literal("x" ), num(Year)], Err(INVALID)); |
757 | check("1234xx1234" , &[num(Year), Literal("xx" ), num(Year)], parsed!(year: 1234)); |
758 | check( |
759 | "1234 x 1234" , |
760 | &[num(Year), Space(" " ), Literal("x" ), Space(" " ), num(Year)], |
761 | parsed!(year: 1234), |
762 | ); |
763 | check( |
764 | "1234 x 1235" , |
765 | &[num(Year), Space(" " ), Literal("x" ), Space(" " ), Literal("1235" )], |
766 | parsed!(year: 1234), |
767 | ); |
768 | |
769 | // signed numeric |
770 | check("-42" , &[num(Year)], parsed!(year: -42)); |
771 | check("+42" , &[num(Year)], parsed!(year: 42)); |
772 | check("-0042" , &[num(Year)], parsed!(year: -42)); |
773 | check("+0042" , &[num(Year)], parsed!(year: 42)); |
774 | check("-42195" , &[num(Year)], parsed!(year: -42195)); |
775 | check("−42195" , &[num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) |
776 | check("+42195" , &[num(Year)], parsed!(year: 42195)); |
777 | check(" -42195" , &[num(Year)], parsed!(year: -42195)); |
778 | check(" +42195" , &[num(Year)], parsed!(year: 42195)); |
779 | check(" -42195" , &[num(Year)], parsed!(year: -42195)); |
780 | check(" +42195" , &[num(Year)], parsed!(year: 42195)); |
781 | check("-42195 " , &[num(Year)], Err(TOO_LONG)); |
782 | check("+42195 " , &[num(Year)], Err(TOO_LONG)); |
783 | check(" - 42" , &[num(Year)], Err(INVALID)); |
784 | check(" + 42" , &[num(Year)], Err(INVALID)); |
785 | check(" -42195" , &[Space(" " ), num(Year)], parsed!(year: -42195)); |
786 | check(" −42195" , &[Space(" " ), num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) |
787 | check(" +42195" , &[Space(" " ), num(Year)], parsed!(year: 42195)); |
788 | check(" - 42" , &[Space(" " ), num(Year)], Err(INVALID)); |
789 | check(" + 42" , &[Space(" " ), num(Year)], Err(INVALID)); |
790 | check("-" , &[num(Year)], Err(TOO_SHORT)); |
791 | check("+" , &[num(Year)], Err(TOO_SHORT)); |
792 | |
793 | // unsigned numeric |
794 | check("345" , &[num(Ordinal)], parsed!(ordinal: 345)); |
795 | check("+345" , &[num(Ordinal)], Err(INVALID)); |
796 | check("-345" , &[num(Ordinal)], Err(INVALID)); |
797 | check(" 345" , &[num(Ordinal)], parsed!(ordinal: 345)); |
798 | check("−345" , &[num(Ordinal)], Err(INVALID)); // MINUS SIGN (U+2212) |
799 | check("345 " , &[num(Ordinal)], Err(TOO_LONG)); |
800 | check(" 345" , &[Space(" " ), num(Ordinal)], parsed!(ordinal: 345)); |
801 | check("345 " , &[num(Ordinal), Space(" " )], parsed!(ordinal: 345)); |
802 | check("345🤠" , &[num(Ordinal), Literal("🤠" ), Space(" " )], parsed!(ordinal: 345)); |
803 | check("345🤠" , &[num(Ordinal)], Err(TOO_LONG)); |
804 | check(" \u{0363}345" , &[num(Ordinal)], Err(INVALID)); |
805 | check(" +345" , &[num(Ordinal)], Err(INVALID)); |
806 | check(" -345" , &[num(Ordinal)], Err(INVALID)); |
807 | check(" \t345" , &[Space(" \t" ), num(Ordinal)], parsed!(ordinal: 345)); |
808 | check(" +345" , &[Space(" " ), num(Ordinal)], Err(INVALID)); |
809 | check(" -345" , &[Space(" " ), num(Ordinal)], Err(INVALID)); |
810 | |
811 | // various numeric fields |
812 | check("1234 5678" , &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); |
813 | check("1234 5678" , &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); |
814 | check( |
815 | "12 34 56 78" , |
816 | &[num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)], |
817 | parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), |
818 | ); |
819 | check( |
820 | "1 2 3 4 5" , |
821 | &[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)], |
822 | parsed!(month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), |
823 | ); |
824 | check( |
825 | "6 7 89 01" , |
826 | &[num(WeekFromMon), num(WeekdayFromMon), num(Ordinal), num(Hour12)], |
827 | parsed!(week_from_mon: 6, weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1), |
828 | ); |
829 | check( |
830 | "23 45 6 78901234 567890123" , |
831 | &[num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)], |
832 | parsed!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123), |
833 | ); |
834 | } |
835 | |
836 | #[test ] |
837 | fn test_parse_fixed() { |
838 | use crate::format::Fixed::*; |
839 | use crate::format::Item::{Literal, Space}; |
840 | |
841 | // fixed: month and weekday names |
842 | check("apr" , &[fixed(ShortMonthName)], parsed!(month: 4)); |
843 | check("Apr" , &[fixed(ShortMonthName)], parsed!(month: 4)); |
844 | check("APR" , &[fixed(ShortMonthName)], parsed!(month: 4)); |
845 | check("ApR" , &[fixed(ShortMonthName)], parsed!(month: 4)); |
846 | check(" \u{0363}APR" , &[fixed(ShortMonthName)], Err(INVALID)); |
847 | check("April" , &[fixed(ShortMonthName)], Err(TOO_LONG)); // `Apr` is parsed |
848 | check("A" , &[fixed(ShortMonthName)], Err(TOO_SHORT)); |
849 | check("Sol" , &[fixed(ShortMonthName)], Err(INVALID)); |
850 | check("Apr" , &[fixed(LongMonthName)], parsed!(month: 4)); |
851 | check("Apri" , &[fixed(LongMonthName)], Err(TOO_LONG)); // `Apr` is parsed |
852 | check("April" , &[fixed(LongMonthName)], parsed!(month: 4)); |
853 | check("Aprill" , &[fixed(LongMonthName)], Err(TOO_LONG)); |
854 | check("Aprill" , &[fixed(LongMonthName), Literal("l" )], parsed!(month: 4)); |
855 | check("Aprl" , &[fixed(LongMonthName), Literal("l" )], parsed!(month: 4)); |
856 | check("April" , &[fixed(LongMonthName), Literal("il" )], Err(TOO_SHORT)); // do not backtrack |
857 | check("thu" , &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); |
858 | check("Thu" , &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); |
859 | check("THU" , &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); |
860 | check("tHu" , &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); |
861 | check("Thursday" , &[fixed(ShortWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed |
862 | check("T" , &[fixed(ShortWeekdayName)], Err(TOO_SHORT)); |
863 | check("The" , &[fixed(ShortWeekdayName)], Err(INVALID)); |
864 | check("Nop" , &[fixed(ShortWeekdayName)], Err(INVALID)); |
865 | check("Thu" , &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); |
866 | check("Thur" , &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed |
867 | check("Thurs" , &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed |
868 | check("Thursday" , &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); |
869 | check("Thursdays" , &[fixed(LongWeekdayName)], Err(TOO_LONG)); |
870 | check("Thursdays" , &[fixed(LongWeekdayName), Literal("s" )], parsed!(weekday: Weekday::Thu)); |
871 | check("Thus" , &[fixed(LongWeekdayName), Literal("s" )], parsed!(weekday: Weekday::Thu)); |
872 | check("Thursday" , &[fixed(LongWeekdayName), Literal("rsday" )], Err(TOO_SHORT)); // do not backtrack |
873 | |
874 | // fixed: am/pm |
875 | check("am" , &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); |
876 | check("pm" , &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); |
877 | check("AM" , &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); |
878 | check("PM" , &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); |
879 | check("am" , &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); |
880 | check("pm" , &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); |
881 | check("AM" , &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); |
882 | check("PM" , &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); |
883 | check("Am" , &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); |
884 | check(" Am" , &[Space(" " ), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); |
885 | check("Am🤠" , &[fixed(LowerAmPm), Literal("🤠" )], parsed!(hour_div_12: 0)); |
886 | check("🤠Am" , &[Literal("🤠" ), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); |
887 | check(" \u{0363}am" , &[fixed(LowerAmPm)], Err(INVALID)); |
888 | check(" \u{0360}am" , &[fixed(LowerAmPm)], Err(INVALID)); |
889 | check(" Am" , &[fixed(LowerAmPm)], Err(INVALID)); |
890 | check("Am " , &[fixed(LowerAmPm)], Err(TOO_LONG)); |
891 | check("a.m." , &[fixed(LowerAmPm)], Err(INVALID)); |
892 | check("A.M." , &[fixed(LowerAmPm)], Err(INVALID)); |
893 | check("ame" , &[fixed(LowerAmPm)], Err(TOO_LONG)); // `am` is parsed |
894 | check("a" , &[fixed(LowerAmPm)], Err(TOO_SHORT)); |
895 | check("p" , &[fixed(LowerAmPm)], Err(TOO_SHORT)); |
896 | check("x" , &[fixed(LowerAmPm)], Err(TOO_SHORT)); |
897 | check("xx" , &[fixed(LowerAmPm)], Err(INVALID)); |
898 | check("" , &[fixed(LowerAmPm)], Err(TOO_SHORT)); |
899 | } |
900 | |
901 | #[test ] |
902 | fn test_parse_fixed_nanosecond() { |
903 | use crate::format::Fixed::Nanosecond; |
904 | use crate::format::InternalInternal::*; |
905 | use crate::format::Item::Literal; |
906 | use crate::format::Numeric::Second; |
907 | |
908 | // fixed: dot plus nanoseconds |
909 | check("" , &[fixed(Nanosecond)], parsed!()); // no field set, but not an error |
910 | check("." , &[fixed(Nanosecond)], Err(TOO_SHORT)); |
911 | check("4" , &[fixed(Nanosecond)], Err(TOO_LONG)); // never consumes `4` |
912 | check("4" , &[fixed(Nanosecond), num(Second)], parsed!(second: 4)); |
913 | check(".0" , &[fixed(Nanosecond)], parsed!(nanosecond: 0)); |
914 | check(".4" , &[fixed(Nanosecond)], parsed!(nanosecond: 400_000_000)); |
915 | check(".42" , &[fixed(Nanosecond)], parsed!(nanosecond: 420_000_000)); |
916 | check(".421" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_000_000)); |
917 | check(".42195" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_000)); |
918 | check(".421951" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_000)); |
919 | check(".4219512" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_200)); |
920 | check(".42195123" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_230)); |
921 | check(".421950803" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); |
922 | check(".4219508035" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); |
923 | check(".42195080354" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); |
924 | check(".421950803547" , &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); |
925 | check(".000000003" , &[fixed(Nanosecond)], parsed!(nanosecond: 3)); |
926 | check(".0000000031" , &[fixed(Nanosecond)], parsed!(nanosecond: 3)); |
927 | check(".0000000035" , &[fixed(Nanosecond)], parsed!(nanosecond: 3)); |
928 | check(".000000003547" , &[fixed(Nanosecond)], parsed!(nanosecond: 3)); |
929 | check(".0000000009" , &[fixed(Nanosecond)], parsed!(nanosecond: 0)); |
930 | check(".000000000547" , &[fixed(Nanosecond)], parsed!(nanosecond: 0)); |
931 | check(".0000000009999999999999999999999999" , &[fixed(Nanosecond)], parsed!(nanosecond: 0)); |
932 | check(".4🤠" , &[fixed(Nanosecond), Literal("🤠" )], parsed!(nanosecond: 400_000_000)); |
933 | check(".4x" , &[fixed(Nanosecond)], Err(TOO_LONG)); |
934 | check(". 4" , &[fixed(Nanosecond)], Err(INVALID)); |
935 | check(" .4" , &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming |
936 | |
937 | // fixed: nanoseconds without the dot |
938 | check("" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
939 | check("." , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
940 | check("0" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
941 | check("4" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
942 | check("42" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
943 | check("421" , &[internal_fixed(Nanosecond3NoDot)], parsed!(nanosecond: 421_000_000)); |
944 | check("4210" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); |
945 | check( |
946 | "42143" , |
947 | &[internal_fixed(Nanosecond3NoDot), num(Second)], |
948 | parsed!(nanosecond: 421_000_000, second: 43), |
949 | ); |
950 | check( |
951 | "421🤠" , |
952 | &[internal_fixed(Nanosecond3NoDot), Literal("🤠" )], |
953 | parsed!(nanosecond: 421_000_000), |
954 | ); |
955 | check( |
956 | "🤠421" , |
957 | &[Literal("🤠" ), internal_fixed(Nanosecond3NoDot)], |
958 | parsed!(nanosecond: 421_000_000), |
959 | ); |
960 | check("42195" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); |
961 | check("123456789" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); |
962 | check("4x" , &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); |
963 | check(" 4" , &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); |
964 | check(".421" , &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); |
965 | |
966 | check("" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
967 | check("." , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
968 | check("0" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
969 | check("1234" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
970 | check("12345" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
971 | check("421950" , &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 421_950_000)); |
972 | check("000003" , &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 3000)); |
973 | check("000000" , &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 0)); |
974 | check("1234567" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); |
975 | check("123456789" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); |
976 | check("4x" , &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); |
977 | check(" 4" , &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); |
978 | check(".42100" , &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); |
979 | |
980 | check("" , &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); |
981 | check("." , &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); |
982 | check("42195" , &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); |
983 | check("12345678" , &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); |
984 | check("421950803" , &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 421_950_803)); |
985 | check("000000003" , &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 3)); |
986 | check( |
987 | "42195080354" , |
988 | &[internal_fixed(Nanosecond9NoDot), num(Second)], |
989 | parsed!(nanosecond: 421_950_803, second: 54), |
990 | ); // don't skip digits that come after the 9 |
991 | check("1234567890" , &[internal_fixed(Nanosecond9NoDot)], Err(TOO_LONG)); |
992 | check("000000000" , &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 0)); |
993 | check("00000000x" , &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); |
994 | check(" 4" , &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); |
995 | check(".42100000" , &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); |
996 | } |
997 | |
998 | #[test ] |
999 | fn test_parse_fixed_timezone_offset() { |
1000 | use crate::format::Fixed::*; |
1001 | use crate::format::InternalInternal::*; |
1002 | use crate::format::Item::Literal; |
1003 | |
1004 | // TimezoneOffset |
1005 | check("1" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1006 | check("12" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1007 | check("123" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1008 | check("1234" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1009 | check("12345" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1010 | check("123456" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1011 | check("1234567" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1012 | check("+1" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1013 | check("+12" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1014 | check("+123" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1015 | check("+1234" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1016 | check("+12345" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1017 | check("+123456" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1018 | check("+1234567" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1019 | check("+12345678" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1020 | check("+12:" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1021 | check("+12:3" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1022 | check("+12:34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1023 | check("-12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1024 | check("−12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1025 | check("+12:34:" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1026 | check("+12:34:5" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1027 | check("+12:34:56" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1028 | check("+12:34:56:" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1029 | check("+12 34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1030 | check("+12 34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1031 | check("12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1032 | check("12:34:56" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1033 | check("+12::34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1034 | check("+12: :34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1035 | check("+12:::34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1036 | check("+12::::34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1037 | check("+12::34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1038 | check("+12:34:56" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1039 | check("+12:3456" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1040 | check("+1234:56" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1041 | check("+1234:567" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1042 | check("+00:00" , &[fixed(TimezoneOffset)], parsed!(offset: 0)); |
1043 | check("-00:00" , &[fixed(TimezoneOffset)], parsed!(offset: 0)); |
1044 | check("−00:00" , &[fixed(TimezoneOffset)], parsed!(offset: 0)); // MINUS SIGN (U+2212) |
1045 | check("+00:01" , &[fixed(TimezoneOffset)], parsed!(offset: 60)); |
1046 | check("-00:01" , &[fixed(TimezoneOffset)], parsed!(offset: -60)); |
1047 | check("+00:30" , &[fixed(TimezoneOffset)], parsed!(offset: 1_800)); |
1048 | check("-00:30" , &[fixed(TimezoneOffset)], parsed!(offset: -1_800)); |
1049 | check("+24:00" , &[fixed(TimezoneOffset)], parsed!(offset: 86_400)); |
1050 | check("-24:00" , &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); |
1051 | check("−24:00" , &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); // MINUS SIGN (U+2212) |
1052 | check("+99:59" , &[fixed(TimezoneOffset)], parsed!(offset: 359_940)); |
1053 | check("-99:59" , &[fixed(TimezoneOffset)], parsed!(offset: -359_940)); |
1054 | check("+00:60" , &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); |
1055 | check("+00:99" , &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); |
1056 | check("#12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1057 | check("+12:34 " , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1058 | check("+12 34 " , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1059 | check(" +12:34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1060 | check(" -12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1061 | check(" −12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1062 | check(" +12:34" , &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1063 | check(" -12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1064 | check(" \t -12:34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1065 | check("-12: 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1066 | check("-12 :34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1067 | check("-12 : 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1068 | check("-12 : 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1069 | check("-12 : 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1070 | check("-12: 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1071 | check("-12 :34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1072 | check("-12 : 34" , &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); |
1073 | check("12:34 " , &[fixed(TimezoneOffset)], Err(INVALID)); |
1074 | check(" 12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1075 | check("" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1076 | check("+" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1077 | check( |
1078 | "+12345" , |
1079 | &[fixed(TimezoneOffset), num(Numeric::Day)], |
1080 | parsed!(offset: 45_240, day: 5), |
1081 | ); |
1082 | check( |
1083 | "+12:345" , |
1084 | &[fixed(TimezoneOffset), num(Numeric::Day)], |
1085 | parsed!(offset: 45_240, day: 5), |
1086 | ); |
1087 | check("+12:34:" , &[fixed(TimezoneOffset), Literal(":" )], parsed!(offset: 45_240)); |
1088 | check("Z12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1089 | check("X12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1090 | check("Z+12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1091 | check("X+12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1092 | check("X−12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); // MINUS SIGN (U+2212) |
1093 | check("🤠+12:34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1094 | check("+12:34🤠" , &[fixed(TimezoneOffset)], Err(TOO_LONG)); |
1095 | check("+12:🤠34" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1096 | check("+1234🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: 45_240)); |
1097 | check("-1234🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: -45_240)); |
1098 | check("−1234🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1099 | check("+12:34🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: 45_240)); |
1100 | check("-12:34🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: -45_240)); |
1101 | check("−12:34🤠" , &[fixed(TimezoneOffset), Literal("🤠" )], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1102 | check("🤠+12:34" , &[Literal("🤠" ), fixed(TimezoneOffset)], parsed!(offset: 45_240)); |
1103 | check("Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1104 | check("A" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1105 | check("PST" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1106 | check("#Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1107 | check(":Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1108 | check("+Z" , &[fixed(TimezoneOffset)], Err(TOO_SHORT)); |
1109 | check("+:Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1110 | check("+Z:" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1111 | check("z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1112 | check(" :Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1113 | check(" Z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1114 | check(" z" , &[fixed(TimezoneOffset)], Err(INVALID)); |
1115 | |
1116 | // TimezoneOffsetColon |
1117 | check("1" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1118 | check("12" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1119 | check("123" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1120 | check("1234" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1121 | check("12345" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1122 | check("123456" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1123 | check("1234567" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1124 | check("12345678" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1125 | check("+1" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1126 | check("+12" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1127 | check("+123" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1128 | check("+1234" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1129 | check("-1234" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); |
1130 | check("−1234" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1131 | check("+12345" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1132 | check("+123456" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1133 | check("+1234567" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1134 | check("+12345678" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1135 | check("1:" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1136 | check("12:" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1137 | check("12:3" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1138 | check("12:34" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1139 | check("12:34:" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1140 | check("12:34:5" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1141 | check("12:34:56" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1142 | check("+1:" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1143 | check("+12:" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1144 | check("+12:3" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1145 | check("+12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1146 | check("-12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); |
1147 | check("−12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1148 | check("+12:34:" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1149 | check("+12:34:5" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1150 | check("+12:34:56" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1151 | check("+12:34:56:" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1152 | check("+12:34:56:7" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1153 | check("+12:34:56:78" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1154 | check("+12:3456" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1155 | check("+1234:56" , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1156 | check("−12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1157 | check("−12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1158 | check("+12 :34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1159 | check("+12: 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1160 | check("+12 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1161 | check("+12: 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1162 | check("+12 :34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1163 | check("+12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1164 | check("-12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); |
1165 | check("+12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1166 | check("+12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1167 | check("+12 : 34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1168 | check("+12::34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1169 | check("+12: :34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1170 | check("+12:::34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1171 | check("+12::::34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1172 | check("+12::34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1173 | check("#1234" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1174 | check("#12:34" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1175 | check("+12:34 " , &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); |
1176 | check(" +12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1177 | check(" \t+12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1178 | check(" \t\t+12:34" , &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); |
1179 | check("12:34 " , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1180 | check(" 12:34" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1181 | check("" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1182 | check("+" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1183 | check(":" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1184 | check( |
1185 | "+12345" , |
1186 | &[fixed(TimezoneOffsetColon), num(Numeric::Day)], |
1187 | parsed!(offset: 45_240, day: 5), |
1188 | ); |
1189 | check( |
1190 | "+12:345" , |
1191 | &[fixed(TimezoneOffsetColon), num(Numeric::Day)], |
1192 | parsed!(offset: 45_240, day: 5), |
1193 | ); |
1194 | check("+12:34:" , &[fixed(TimezoneOffsetColon), Literal(":" )], parsed!(offset: 45_240)); |
1195 | check("Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1196 | check("A" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1197 | check("PST" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1198 | check("#Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1199 | check(":Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1200 | check("+Z" , &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); |
1201 | check("+:Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1202 | check("+Z:" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1203 | check("z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1204 | check(" :Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1205 | check(" Z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1206 | check(" z" , &[fixed(TimezoneOffsetColon)], Err(INVALID)); |
1207 | // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` |
1208 | // and `TimezoneOffsetTripleColon` for function `parse_internal`. |
1209 | // No need for separate tests for `TimezoneOffsetDoubleColon` and |
1210 | // `TimezoneOffsetTripleColon`. |
1211 | |
1212 | // TimezoneOffsetZ |
1213 | check("1" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1214 | check("12" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1215 | check("123" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1216 | check("1234" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1217 | check("12345" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1218 | check("123456" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1219 | check("1234567" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1220 | check("12345678" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1221 | check("+1" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1222 | check("+12" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1223 | check("+123" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1224 | check("+1234" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1225 | check("-1234" , &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); |
1226 | check("−1234" , &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1227 | check("+12345" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1228 | check("+123456" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1229 | check("+1234567" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1230 | check("+12345678" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1231 | check("1:" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1232 | check("12:" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1233 | check("12:3" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1234 | check("12:34" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1235 | check("12:34:" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1236 | check("12:34:5" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1237 | check("12:34:56" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1238 | check("+1:" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1239 | check("+12:" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1240 | check("+12:3" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1241 | check("+12:34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1242 | check("-12:34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); |
1243 | check("−12:34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1244 | check("+12:34:" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1245 | check("+12:34:5" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1246 | check("+12:34:56" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1247 | check("+12:34:56:" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1248 | check("+12:34:56:7" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1249 | check("+12:34:56:78" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1250 | check("+12::34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1251 | check("+12:3456" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1252 | check("+1234:56" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1253 | check("+12 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1254 | check("+12 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1255 | check("+12: 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1256 | check("+12 :34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1257 | check("+12 : 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1258 | check("+12 : 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1259 | check("+12 : 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1260 | check("+12 : 34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1261 | check("12:34 " , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1262 | check(" 12:34" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1263 | check("+12:34 " , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1264 | check("+12 34 " , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1265 | check(" +12:34" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); |
1266 | check( |
1267 | "+12345" , |
1268 | &[fixed(TimezoneOffsetZ), num(Numeric::Day)], |
1269 | parsed!(offset: 45_240, day: 5), |
1270 | ); |
1271 | check( |
1272 | "+12:345" , |
1273 | &[fixed(TimezoneOffsetZ), num(Numeric::Day)], |
1274 | parsed!(offset: 45_240, day: 5), |
1275 | ); |
1276 | check("+12:34:" , &[fixed(TimezoneOffsetZ), Literal(":" )], parsed!(offset: 45_240)); |
1277 | check("Z12:34" , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1278 | check("X12:34" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1279 | check("Z" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); |
1280 | check("z" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); |
1281 | check(" Z" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); |
1282 | check(" z" , &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); |
1283 | check(" \u{0363}Z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1284 | check("Z " , &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); |
1285 | check("A" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1286 | check("PST" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1287 | check("#Z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1288 | check(":Z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1289 | check(":z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1290 | check("+Z" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1291 | check("-Z" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1292 | check("+A" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1293 | check("+🙃" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1294 | check("+Z:" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1295 | check(" :Z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1296 | check(" +Z" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1297 | check(" -Z" , &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); |
1298 | check("+:Z" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1299 | check("Y" , &[fixed(TimezoneOffsetZ)], Err(INVALID)); |
1300 | check("Zulu" , &[fixed(TimezoneOffsetZ), Literal("ulu" )], parsed!(offset: 0)); |
1301 | check("zulu" , &[fixed(TimezoneOffsetZ), Literal("ulu" )], parsed!(offset: 0)); |
1302 | check("+1234ulu" , &[fixed(TimezoneOffsetZ), Literal("ulu" )], parsed!(offset: 45_240)); |
1303 | check("+12:34ulu" , &[fixed(TimezoneOffsetZ), Literal("ulu" )], parsed!(offset: 45_240)); |
1304 | // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` |
1305 | // in function `parse_internal`. |
1306 | // No need for separate tests for `TimezoneOffsetColonZ`. |
1307 | |
1308 | // TimezoneOffsetPermissive |
1309 | check("1" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1310 | check("12" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1311 | check("123" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1312 | check("1234" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1313 | check("12345" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1314 | check("123456" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1315 | check("1234567" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1316 | check("12345678" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1317 | check("+1" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1318 | check("+12" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); |
1319 | check("+123" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1320 | check("+1234" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1321 | check("-1234" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); |
1322 | check("−1234" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1323 | check("+12345" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1324 | check("+123456" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1325 | check("+1234567" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1326 | check("+12345678" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1327 | check("1:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1328 | check("12:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1329 | check("12:3" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1330 | check("12:34" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1331 | check("12:34:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1332 | check("12:34:5" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1333 | check("12:34:56" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1334 | check("+1:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1335 | check("+12:" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); |
1336 | check("+12:3" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1337 | check("+12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1338 | check("-12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); |
1339 | check("−12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1340 | check("+12:34:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1341 | check("+12:34:5" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1342 | check("+12:34:56" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1343 | check("+12:34:56:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1344 | check("+12:34:56:7" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1345 | check("+12:34:56:78" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1346 | check("+12 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1347 | check("+12 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1348 | check("+12 :34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1349 | check("+12: 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1350 | check("+12 : 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1351 | check("+12 :34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1352 | check("+12: 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1353 | check("+12 : 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1354 | check("+12::34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1355 | check("+12 ::34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1356 | check("+12: :34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1357 | check("+12:: 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1358 | check("+12 ::34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1359 | check("+12: :34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1360 | check("+12:: 34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1361 | check("+12:::34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1362 | check("+12::::34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1363 | check("12:34 " , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1364 | check(" 12:34" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1365 | check("+12:34 " , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1366 | check(" +12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); |
1367 | check(" -12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); |
1368 | check(" −12:34" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) |
1369 | check( |
1370 | "+12345" , |
1371 | &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], |
1372 | parsed!(offset: 45_240, day: 5), |
1373 | ); |
1374 | check( |
1375 | "+12:345" , |
1376 | &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], |
1377 | parsed!(offset: 45_240, day: 5), |
1378 | ); |
1379 | check( |
1380 | "+12:34:" , |
1381 | &[internal_fixed(TimezoneOffsetPermissive), Literal(":" )], |
1382 | parsed!(offset: 45_240), |
1383 | ); |
1384 | check("🤠+12:34" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1385 | check("+12:34🤠" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1386 | check("+12:🤠34" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1387 | check( |
1388 | "+12:34🤠" , |
1389 | &[internal_fixed(TimezoneOffsetPermissive), Literal("🤠" )], |
1390 | parsed!(offset: 45_240), |
1391 | ); |
1392 | check( |
1393 | "🤠+12:34" , |
1394 | &[Literal("🤠" ), internal_fixed(TimezoneOffsetPermissive)], |
1395 | parsed!(offset: 45_240), |
1396 | ); |
1397 | check("Z" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); |
1398 | check("A" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1399 | check("PST" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1400 | check("z" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); |
1401 | check(" Z" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); |
1402 | check(" z" , &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); |
1403 | check("Z " , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); |
1404 | check("#Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1405 | check(":Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1406 | check(":z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1407 | check("+Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1408 | check("-Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1409 | check("+A" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1410 | check("+PST" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1411 | check("+🙃" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1412 | check("+Z:" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1413 | check(" :Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1414 | check(" +Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1415 | check(" -Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); |
1416 | check("+:Z" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1417 | check("Y" , &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); |
1418 | |
1419 | // TimezoneName |
1420 | check("CEST" , &[fixed(TimezoneName)], parsed!()); |
1421 | check("cest" , &[fixed(TimezoneName)], parsed!()); // lowercase |
1422 | check("XXXXXXXX" , &[fixed(TimezoneName)], parsed!()); // not a real timezone name |
1423 | check("!!!!" , &[fixed(TimezoneName)], parsed!()); // not a real timezone name! |
1424 | check("CEST 5" , &[fixed(TimezoneName), Literal(" " ), num(Numeric::Day)], parsed!(day: 5)); |
1425 | check("CEST " , &[fixed(TimezoneName)], Err(TOO_LONG)); |
1426 | check(" CEST" , &[fixed(TimezoneName)], Err(TOO_LONG)); |
1427 | check("CE ST" , &[fixed(TimezoneName)], Err(TOO_LONG)); |
1428 | } |
1429 | |
1430 | #[test ] |
1431 | #[rustfmt::skip] |
1432 | fn test_parse_practical_examples() { |
1433 | use crate::format::InternalInternal::*; |
1434 | use crate::format::Item::{Literal, Space}; |
1435 | use crate::format::Numeric::*; |
1436 | |
1437 | // some practical examples |
1438 | check( |
1439 | "2015-02-04T14:37:05+09:00" , |
1440 | &[ |
1441 | num(Year), Literal("-" ), num(Month), Literal("-" ), num(Day), Literal("T" ), |
1442 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), |
1443 | fixed(Fixed::TimezoneOffset), |
1444 | ], |
1445 | parsed!( |
1446 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, |
1447 | second: 5, offset: 32400 |
1448 | ), |
1449 | ); |
1450 | check( |
1451 | "2015-02-04T14:37:05-09:00" , |
1452 | &[ |
1453 | num(Year), Literal("-" ), num(Month), Literal("-" ), num(Day), Literal("T" ), |
1454 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), |
1455 | fixed(Fixed::TimezoneOffset), |
1456 | ], |
1457 | parsed!( |
1458 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, |
1459 | second: 5, offset: -32400 |
1460 | ), |
1461 | ); |
1462 | check( |
1463 | "2015-02-04T14:37:05−09:00" , // timezone offset using MINUS SIGN (U+2212) |
1464 | &[ |
1465 | num(Year), Literal("-" ), num(Month), Literal("-" ), num(Day), Literal("T" ), |
1466 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), |
1467 | fixed(Fixed::TimezoneOffset) |
1468 | ], |
1469 | parsed!( |
1470 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, |
1471 | second: 5, offset: -32400 |
1472 | ), |
1473 | ); |
1474 | check( |
1475 | "20150204143705567" , |
1476 | &[ |
1477 | num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second), |
1478 | internal_fixed(Nanosecond3NoDot) |
1479 | ], |
1480 | parsed!( |
1481 | year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, |
1482 | second: 5, nanosecond: 567000000 |
1483 | ), |
1484 | ); |
1485 | check( |
1486 | "Mon, 10 Jun 2013 09:32:37 GMT" , |
1487 | &[ |
1488 | fixed(Fixed::ShortWeekdayName), Literal("," ), Space(" " ), num(Day), Space(" " ), |
1489 | fixed(Fixed::ShortMonthName), Space(" " ), num(Year), Space(" " ), num(Hour), |
1490 | Literal(":" ), num(Minute), Literal(":" ), num(Second), Space(" " ), Literal("GMT" ) |
1491 | ], |
1492 | parsed!( |
1493 | year: 2013, month: 6, day: 10, weekday: Weekday::Mon, |
1494 | hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 |
1495 | ), |
1496 | ); |
1497 | check( |
1498 | "🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠" , |
1499 | &[ |
1500 | Literal("🤠" ), fixed(Fixed::ShortWeekdayName), Literal("," ), Space(" " ), num(Day), |
1501 | Space(" " ), fixed(Fixed::ShortMonthName), Literal("🤠" ), num(Year), Space(" " ), |
1502 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), Space(" " ), |
1503 | Literal("GMT" ), Literal("🤠" ) |
1504 | ], |
1505 | parsed!( |
1506 | year: 2013, month: 6, day: 10, weekday: Weekday::Mon, |
1507 | hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 |
1508 | ), |
1509 | ); |
1510 | check( |
1511 | "Sun Aug 02 13:39:15 CEST 2020" , |
1512 | &[ |
1513 | fixed(Fixed::ShortWeekdayName), Space(" " ), fixed(Fixed::ShortMonthName), |
1514 | Space(" " ), num(Day), Space(" " ), num(Hour), Literal(":" ), num(Minute), |
1515 | Literal(":" ), num(Second), Space(" " ), fixed(Fixed::TimezoneName), Space(" " ), |
1516 | num(Year) |
1517 | ], |
1518 | parsed!( |
1519 | year: 2020, month: 8, day: 2, weekday: Weekday::Sun, |
1520 | hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15 |
1521 | ), |
1522 | ); |
1523 | check( |
1524 | "20060102150405" , |
1525 | &[num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)], |
1526 | parsed!( |
1527 | year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5 |
1528 | ), |
1529 | ); |
1530 | check( |
1531 | "3:14PM" , |
1532 | &[num(Hour12), Literal(":" ), num(Minute), fixed(Fixed::LowerAmPm)], |
1533 | parsed!(hour_div_12: 1, hour_mod_12: 3, minute: 14), |
1534 | ); |
1535 | check( |
1536 | "12345678901234.56789" , |
1537 | &[num(Timestamp), Literal("." ), num(Nanosecond)], |
1538 | parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234), |
1539 | ); |
1540 | check( |
1541 | "12345678901234.56789" , |
1542 | &[num(Timestamp), fixed(Fixed::Nanosecond)], |
1543 | parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234), |
1544 | ); |
1545 | |
1546 | // docstring examples from `impl str::FromStr` |
1547 | check( |
1548 | "2000-01-02T03:04:05Z" , |
1549 | &[ |
1550 | num(Year), Literal("-" ), num(Month), Literal("-" ), num(Day), Literal("T" ), |
1551 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), |
1552 | internal_fixed(TimezoneOffsetPermissive) |
1553 | ], |
1554 | parsed!( |
1555 | year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, |
1556 | offset: 0 |
1557 | ), |
1558 | ); |
1559 | check( |
1560 | "2000-01-02 03:04:05Z" , |
1561 | &[ |
1562 | num(Year), Literal("-" ), num(Month), Literal("-" ), num(Day), Space(" " ), |
1563 | num(Hour), Literal(":" ), num(Minute), Literal(":" ), num(Second), |
1564 | internal_fixed(TimezoneOffsetPermissive) |
1565 | ], |
1566 | parsed!( |
1567 | year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, |
1568 | offset: 0 |
1569 | ), |
1570 | ); |
1571 | } |
1572 | |
1573 | #[track_caller ] |
1574 | fn parses(s: &str, items: &[Item]) { |
1575 | let mut parsed = Parsed::new(); |
1576 | assert!(parse(&mut parsed, s, items.iter()).is_ok()); |
1577 | } |
1578 | |
1579 | #[track_caller ] |
1580 | fn check(s: &str, items: &[Item], expected: ParseResult<Parsed>) { |
1581 | let mut parsed = Parsed::new(); |
1582 | let result = parse(&mut parsed, s, items.iter()); |
1583 | let parsed = result.map(|_| parsed); |
1584 | assert_eq!(parsed, expected); |
1585 | } |
1586 | |
1587 | #[test ] |
1588 | fn test_rfc2822() { |
1589 | let ymd_hmsn = |y, m, d, h, n, s, nano, off| { |
1590 | FixedOffset::east_opt(off * 60 * 60) |
1591 | .unwrap() |
1592 | .with_ymd_and_hms(y, m, d, h, n, s) |
1593 | .unwrap() |
1594 | .with_nanosecond(nano) |
1595 | .unwrap() |
1596 | }; |
1597 | |
1598 | // Test data - (input, Ok(expected result) or Err(error code)) |
1599 | let testdates = [ |
1600 | ("Tue, 20 Jan 2015 17:35:20 -0800" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case |
1601 | ("Fri, 2 Jan 2015 17:35:20 -0800" , Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace |
1602 | ("Fri, 02 Jan 2015 17:35:20 -0800" , Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero |
1603 | ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment |
1604 | ( |
1605 | r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))" , |
1606 | Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), |
1607 | ), // complex trailing comment |
1608 | (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)" , Err(TOO_LONG)), // incorrect comment, not enough closing parentheses |
1609 | ( |
1610 | "Tue, 20 Jan 2015 17:35:20 -0800 (UTC) \t \r\n(Anothercomment)" , |
1611 | Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), |
1612 | ), // multiple comments |
1613 | ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) " , Err(TOO_LONG)), // trailing whitespace after comment |
1614 | ("20 Jan 2015 17:35:20 -0800" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week |
1615 | ("20 JAN 2015 17:35:20 -0800" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month |
1616 | ("Tue, 20 Jan 2015 17:35 -0800" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second |
1617 | ("11 Sep 2001 09:45:00 +0000" , Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), |
1618 | ("11 Sep 2001 09:45:00 EST" , Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), |
1619 | ("11 Sep 2001 09:45:00 GMT" , Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), |
1620 | ("30 Feb 2015 17:35:20 -0800" , Err(OUT_OF_RANGE)), // bad day of month |
1621 | ("Tue, 20 Jan 2015" , Err(TOO_SHORT)), // omitted fields |
1622 | ("Tue, 20 Avr 2015 17:35:20 -0800" , Err(INVALID)), // bad month name |
1623 | ("Tue, 20 Jan 2015 25:35:20 -0800" , Err(OUT_OF_RANGE)), // bad hour |
1624 | ("Tue, 20 Jan 2015 7:35:20 -0800" , Err(INVALID)), // bad # of digits in hour |
1625 | ("Tue, 20 Jan 2015 17:65:20 -0800" , Err(OUT_OF_RANGE)), // bad minute |
1626 | ("Tue, 20 Jan 2015 17:35:90 -0800" , Err(OUT_OF_RANGE)), // bad second |
1627 | ("Tue, 20 Jan 2015 17:35:20 -0890" , Err(OUT_OF_RANGE)), // bad offset |
1628 | ("6 Jun 1944 04:00:00Z" , Err(INVALID)), // bad offset (zulu not allowed) |
1629 | // named timezones that have specific timezone offsets |
1630 | // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 |
1631 | ("Tue, 20 Jan 2015 17:35:20 GMT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1632 | ("Tue, 20 Jan 2015 17:35:20 UT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1633 | ("Tue, 20 Jan 2015 17:35:20 ut" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1634 | ("Tue, 20 Jan 2015 17:35:20 EDT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -4))), |
1635 | ("Tue, 20 Jan 2015 17:35:20 EST" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), |
1636 | ("Tue, 20 Jan 2015 17:35:20 CDT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), |
1637 | ("Tue, 20 Jan 2015 17:35:20 CST" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), |
1638 | ("Tue, 20 Jan 2015 17:35:20 MDT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), |
1639 | ("Tue, 20 Jan 2015 17:35:20 MST" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), |
1640 | ("Tue, 20 Jan 2015 17:35:20 PDT" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), |
1641 | ("Tue, 20 Jan 2015 17:35:20 PST" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), |
1642 | ("Tue, 20 Jan 2015 17:35:20 pst" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), |
1643 | // named single-letter military timezones must fallback to +0000 |
1644 | ("Tue, 20 Jan 2015 17:35:20 Z" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1645 | ("Tue, 20 Jan 2015 17:35:20 A" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1646 | ("Tue, 20 Jan 2015 17:35:20 a" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1647 | ("Tue, 20 Jan 2015 17:35:20 K" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1648 | ("Tue, 20 Jan 2015 17:35:20 k" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), |
1649 | // named single-letter timezone "J" is specifically not valid |
1650 | ("Tue, 20 Jan 2015 17:35:20 J" , Err(INVALID)), |
1651 | ("Tue, 20 Jan 2015 17:35:20 -0890" , Err(OUT_OF_RANGE)), // bad offset minutes |
1652 | ("Tue, 20 Jan 2015 17:35:20Z" , Err(INVALID)), // bad offset: zulu not allowed |
1653 | ("Tue, 20 Jan 2015 17:35:20 Zulu" , Err(INVALID)), // bad offset: zulu not allowed |
1654 | ("Tue, 20 Jan 2015 17:35:20 ZULU" , Err(INVALID)), // bad offset: zulu not allowed |
1655 | ("Tue, 20 Jan 2015 17:35:20 −0800" , Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 |
1656 | ("Tue, 20 Jan 2015 17:35:20 0800" , Err(INVALID)), // missing offset sign |
1657 | ("Tue, 20 Jan 2015 17:35:20 HAS" , Err(INVALID)), // bad named timezone |
1658 | ("Tue, 20 Jan 2015😈17:35:20 -0800" , Err(INVALID)), // bad character! |
1659 | ]; |
1660 | |
1661 | fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> { |
1662 | let mut parsed = Parsed::new(); |
1663 | parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; |
1664 | parsed.to_datetime() |
1665 | } |
1666 | |
1667 | // Test against test data above |
1668 | for &(date, checkdate) in testdates.iter() { |
1669 | #[cfg (feature = "std" )] |
1670 | eprintln!("Test input: {:?} \n Expect: {:?}" , date, checkdate); |
1671 | let dt = rfc2822_to_datetime(date); // parse a date |
1672 | if dt != checkdate { |
1673 | // check for expected result |
1674 | panic!( |
1675 | "Date conversion failed for {} \nReceived: {:?} \nExpected: {:?}" , |
1676 | date, dt, checkdate |
1677 | ); |
1678 | } |
1679 | } |
1680 | } |
1681 | |
1682 | #[test ] |
1683 | fn parse_rfc850() { |
1684 | static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT" ; |
1685 | |
1686 | let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); |
1687 | |
1688 | // Check that the format is what we expect |
1689 | #[cfg (feature = "alloc" )] |
1690 | assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT" ); |
1691 | |
1692 | // Check that it parses correctly |
1693 | assert_eq!( |
1694 | NaiveDateTime::parse_from_str("Sunday, 06-Nov-94 08:49:37 GMT" , RFC850_FMT), |
1695 | Ok(dt.naive_utc()) |
1696 | ); |
1697 | |
1698 | // Check that the rest of the weekdays parse correctly (this test originally failed because |
1699 | // Sunday parsed incorrectly). |
1700 | let testdates = [ |
1701 | ( |
1702 | Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), |
1703 | "Monday, 07-Nov-94 08:49:37 GMT" , |
1704 | ), |
1705 | ( |
1706 | Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), |
1707 | "Tuesday, 08-Nov-94 08:49:37 GMT" , |
1708 | ), |
1709 | ( |
1710 | Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), |
1711 | "Wednesday, 09-Nov-94 08:49:37 GMT" , |
1712 | ), |
1713 | ( |
1714 | Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), |
1715 | "Thursday, 10-Nov-94 08:49:37 GMT" , |
1716 | ), |
1717 | ( |
1718 | Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), |
1719 | "Friday, 11-Nov-94 08:49:37 GMT" , |
1720 | ), |
1721 | ( |
1722 | Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), |
1723 | "Saturday, 12-Nov-94 08:49:37 GMT" , |
1724 | ), |
1725 | ]; |
1726 | |
1727 | for val in &testdates { |
1728 | assert_eq!(NaiveDateTime::parse_from_str(val.1, RFC850_FMT), Ok(val.0.naive_utc())); |
1729 | } |
1730 | |
1731 | let test_dates_fail = [ |
1732 | "Saturday, 12-Nov-94 08:49:37" , |
1733 | "Saturday, 12-Nov-94 08:49:37 Z" , |
1734 | "Saturday, 12-Nov-94 08:49:37 GMTTTT" , |
1735 | "Saturday, 12-Nov-94 08:49:37 gmt" , |
1736 | "Saturday, 12-Nov-94 08:49:37 +08:00" , |
1737 | "Caturday, 12-Nov-94 08:49:37 GMT" , |
1738 | "Saturday, 99-Nov-94 08:49:37 GMT" , |
1739 | "Saturday, 12-Nov-2000 08:49:37 GMT" , |
1740 | "Saturday, 12-Mop-94 08:49:37 GMT" , |
1741 | "Saturday, 12-Nov-94 28:49:37 GMT" , |
1742 | "Saturday, 12-Nov-94 08:99:37 GMT" , |
1743 | "Saturday, 12-Nov-94 08:49:99 GMT" , |
1744 | ]; |
1745 | |
1746 | for val in &test_dates_fail { |
1747 | assert!(NaiveDateTime::parse_from_str(val, RFC850_FMT).is_err()); |
1748 | } |
1749 | } |
1750 | |
1751 | #[test ] |
1752 | fn test_rfc3339() { |
1753 | let ymd_hmsn = |y, m, d, h, n, s, nano, off| { |
1754 | FixedOffset::east_opt(off * 60 * 60) |
1755 | .unwrap() |
1756 | .with_ymd_and_hms(y, m, d, h, n, s) |
1757 | .unwrap() |
1758 | .with_nanosecond(nano) |
1759 | .unwrap() |
1760 | }; |
1761 | |
1762 | // Test data - (input, Ok(expected result) or Err(error code)) |
1763 | let testdates = [ |
1764 | ("2015-01-20T17:35:20-08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case |
1765 | ("2015-01-20T17:35:20−08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case with MINUS SIGN (U+2212) |
1766 | ("1944-06-06T04:04:00Z" , Ok(ymd_hmsn(1944, 6, 6, 4, 4, 0, 0, 0))), // D-day |
1767 | ("2001-09-11T09:45:00-08:00" , Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -8))), |
1768 | ("2015-01-20T17:35:20.001-08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), |
1769 | ("2015-01-20T17:35:20.001−08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), // with MINUS SIGN (U+2212) |
1770 | ("2015-01-20T17:35:20.000031-08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 31_000, -8))), |
1771 | ("2015-01-20T17:35:20.000000004-08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), |
1772 | ("2015-01-20T17:35:20.000000004−08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), // with MINUS SIGN (U+2212) |
1773 | ( |
1774 | "2015-01-20T17:35:20.000000000452-08:00" , |
1775 | Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), |
1776 | ), // too small |
1777 | ( |
1778 | "2015-01-20T17:35:20.000000000452−08:00" , |
1779 | Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), |
1780 | ), // too small with MINUS SIGN (U+2212) |
1781 | ("2015-01-20 17:35:20-08:00" , Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T' |
1782 | ("2015/01/20T17:35:20.001-08:00" , Err(INVALID)), // wrong separator char YMD |
1783 | ("2015-01-20T17-35-20.001-08:00" , Err(INVALID)), // wrong separator char HMS |
1784 | ("-01-20T17:35:20-08:00" , Err(INVALID)), // missing year |
1785 | ("99-01-20T17:35:20-08:00" , Err(INVALID)), // bad year format |
1786 | ("99999-01-20T17:35:20-08:00" , Err(INVALID)), // bad year value |
1787 | ("-2000-01-20T17:35:20-08:00" , Err(INVALID)), // bad year value |
1788 | ("2015-02-30T17:35:20-08:00" , Err(OUT_OF_RANGE)), // bad day of month value |
1789 | ("2015-01-20T25:35:20-08:00" , Err(OUT_OF_RANGE)), // bad hour value |
1790 | ("2015-01-20T17:65:20-08:00" , Err(OUT_OF_RANGE)), // bad minute value |
1791 | ("2015-01-20T17:35:90-08:00" , Err(OUT_OF_RANGE)), // bad second value |
1792 | ("2015-01-20T17:35:20-24:00" , Err(OUT_OF_RANGE)), // bad offset value |
1793 | ("15-01-20T17:35:20-08:00" , Err(INVALID)), // bad year format |
1794 | ("15-01-20T17:35:20-08:00:00" , Err(INVALID)), // bad year format, bad offset format |
1795 | ("2015-01-20T17:35:2008:00" , Err(INVALID)), // missing offset sign |
1796 | ("2015-01-20T17:35:20 08:00" , Err(INVALID)), // missing offset sign |
1797 | ("2015-01-20T17:35:20Zulu" , Err(TOO_LONG)), // bad offset format |
1798 | ("2015-01-20T17:35:20 Zulu" , Err(INVALID)), // bad offset format |
1799 | ("2015-01-20T17:35:20GMT" , Err(INVALID)), // bad offset format |
1800 | ("2015-01-20T17:35:20 GMT" , Err(INVALID)), // bad offset format |
1801 | ("2015-01-20T17:35:20+GMT" , Err(INVALID)), // bad offset format |
1802 | ("2015-01-20T17:35:20++08:00" , Err(INVALID)), // bad offset format |
1803 | ("2015-01-20T17:35:20--08:00" , Err(INVALID)), // bad offset format |
1804 | ("2015-01-20T17:35:20−−08:00" , Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) |
1805 | ("2015-01-20T17:35:20±08:00" , Err(INVALID)), // bad offset sign |
1806 | ("2015-01-20T17:35:20-08-00" , Err(INVALID)), // bad offset separator |
1807 | ("2015-01-20T17:35:20-08;00" , Err(INVALID)), // bad offset separator |
1808 | ("2015-01-20T17:35:20-0800" , Err(INVALID)), // bad offset separator |
1809 | ("2015-01-20T17:35:20-08:0" , Err(TOO_SHORT)), // bad offset minutes |
1810 | ("2015-01-20T17:35:20-08:AA" , Err(INVALID)), // bad offset minutes |
1811 | ("2015-01-20T17:35:20-08:ZZ" , Err(INVALID)), // bad offset minutes |
1812 | ("2015-01-20T17:35:20.001-08 : 00" , Err(INVALID)), // bad offset separator |
1813 | ("2015-01-20T17:35:20-08:00:00" , Err(TOO_LONG)), // bad offset format |
1814 | ("2015-01-20T17:35:20+08:" , Err(TOO_SHORT)), // bad offset format |
1815 | ("2015-01-20T17:35:20-08:" , Err(TOO_SHORT)), // bad offset format |
1816 | ("2015-01-20T17:35:20−08:" , Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) |
1817 | ("2015-01-20T17:35:20-08" , Err(TOO_SHORT)), // bad offset format |
1818 | ("2015-01-20T" , Err(TOO_SHORT)), // missing HMS |
1819 | ("2015-01-20T00:00:1" , Err(TOO_SHORT)), // missing complete S |
1820 | ("2015-01-20T00:00:1-08:00" , Err(INVALID)), // missing complete S |
1821 | ]; |
1822 | |
1823 | // Test against test data above |
1824 | for &(date, checkdate) in testdates.iter() { |
1825 | let dt = DateTime::<FixedOffset>::parse_from_rfc3339(date); |
1826 | if dt != checkdate { |
1827 | // check for expected result |
1828 | panic!( |
1829 | "Date conversion failed for {} \nReceived: {:?} \nExpected: {:?}" , |
1830 | date, dt, checkdate |
1831 | ); |
1832 | } |
1833 | } |
1834 | } |
1835 | |
1836 | #[test ] |
1837 | fn test_issue_1010() { |
1838 | 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}" , |
1839 | " \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" ); |
1840 | assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); |
1841 | } |
1842 | } |
1843 | |