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