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