diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 01258a6c05..5d893e0858 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -809,12 +809,11 @@ fn test_parse_datetime_utc() { "2001-02-03T04:05:06Z", "2001-02-03T04:05:06+0000", "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-00 00", "2001-02-03T04:05:06-01:00", "2001-02-03T04:05:06-01: 00", "2001-02-03T04:05:06-01 :00", "2001-02-03T04:05:06-01 : 00", - "2001-02-03T04:05:06-01 : 00", - "2001-02-03T04:05:06-01 : :00", "2012-12-12T12:12:12Z", "2015-02-18T23:16:09.153Z", "2015-2-18T23:16:09.153Z", @@ -886,6 +885,8 @@ fn test_parse_datetime_utc() { "2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider "2012-12-12T12:12:12Z ", // trailing space " 2012-12-12T12:12:12Z", // leading space + "2001-02-03T04:05:06-01 : 00", // invalid timezone spacing + "2001-02-03T04:05:06-01 : :00", // invalid timezone spacing " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; for &s in invalid.iter() { @@ -1116,13 +1117,11 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -0900::", @@ -1189,13 +1188,11 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %:z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); // timezone data hs too many colons assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00:", @@ -1246,13 +1243,16 @@ fn test_datetime_parse_from_str() { "%b %d %Y %H:%M:%S %::z" ) .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); // wrong timezone data assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09", diff --git a/src/format/parse.rs b/src/format/parse.rs index ba0f00129c..91673af9a4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -452,14 +452,15 @@ where | &TimezoneOffsetTripleColon | &TimezoneOffset => { s = scan::trim1(s); - let offset = try_consume!(scan::timezone_offset(s, scan::colon_or_space)); + let offset = + try_consume!(scan::timezone_offset(s, scan::maybe_colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { s = scan::trim1(s); let offset = - try_consume!(scan::timezone_offset_zulu(s, scan::colon_or_space)); + try_consume!(scan::timezone_offset_zulu(s, scan::maybe_colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -467,8 +468,10 @@ where val: InternalInternal::TimezoneOffsetPermissive, }) => { s = scan::trim1(s); - let offset = - try_consume!(scan::timezone_offset_permissive(s, scan::colon_or_space)); + let offset = try_consume!(scan::timezone_offset_permissive( + s, + scan::maybe_colon_or_space + )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -927,14 +930,14 @@ fn test_parse() { check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; INVALID); check!("12:34", [fix!(TimezoneOffset)]; INVALID); check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12: :34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12::::34", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; INVALID); check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); @@ -962,11 +965,11 @@ fn test_parse() { check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12: 34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 :34", [fix!(TimezoneOffset)]; INVALID); + check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID); check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); check!("", [fix!(TimezoneOffset)]; TOO_SHORT); @@ -1038,14 +1041,14 @@ fn test_parse() { check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID); check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -1113,17 +1116,17 @@ fn test_parse() { check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID); check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1200,22 +1203,22 @@ fn test_parse() { check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); diff --git a/src/format/scan.rs b/src/format/scan.rs index 28c647a7a5..ffbf654b32 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -219,9 +219,46 @@ pub(super) fn trim1(s: &str) -> &str { } } -/// Consumes any number (including zero) of colon or spaces. -pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) +/// Is `s.next()` whitespace? +/// Helper function to `maybe_colon_or_space`. +fn next_is_whitespace(s: &str) -> bool { + s.chars().next().map(|c| c.is_whitespace()).unwrap_or_default() +} + +/// Allow a colon with possible one-character whitespace padding. +/// Consumes zero or one of these leading patterns: +/// `":"`, `" "`, `" :"`, `": "`, or `" : "`. +/// Always returns `Ok(s)`. +pub(super) fn maybe_colon_or_space(mut s: &str) -> ParseResult<&str> { + if s.is_empty() { + // nothing consumed + return Ok(s); + } + + if s.starts_with(':') { + s = s_next(s); + if next_is_whitespace(s) { + s = s_next(s); + } + // consumed `":"` or `": "` + return Ok(s); + } else if !next_is_whitespace(s) { + return Ok(s); + } + + s = s_next(s); + if s.starts_with(':') { + s = s_next(s); + } else { + // consumed `" "` + return Ok(s); + } + if next_is_whitespace(s) { + s = s_next(s); + } + + // consumed `" :"` or `" : "` + Ok(s) } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -464,3 +501,50 @@ fn test_trim1() { assert_eq!(trim1("šŸ˜¼"), "šŸ˜¼"); assert_eq!(trim1("šŸ˜¼b"), "šŸ˜¼b"); } + +#[test] +fn test_next_is_whitespace() { + assert!(!next_is_whitespace("")); + assert!(!next_is_whitespace("a")); + assert!(!next_is_whitespace("šŸ˜¼šŸ˜¼")); + assert!(next_is_whitespace(" ")); + assert!(next_is_whitespace("\t\t")); + assert!(next_is_whitespace("\ta\t")); + assert!(next_is_whitespace("\tšŸ˜¼\t")); +} + +#[test] +fn test_maybe_colon_or_space() { + assert_eq!(maybe_colon_or_space(""), Ok("")); + assert_eq!(maybe_colon_or_space(" "), Ok("")); + assert_eq!(maybe_colon_or_space("\n"), Ok("")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" "), Ok(" ")); + assert_eq!(maybe_colon_or_space("\t\t\t\t"), Ok("\t\t\t")); + assert_eq!(maybe_colon_or_space(":"), Ok("")); + assert_eq!(maybe_colon_or_space(" :"), Ok("")); + assert_eq!(maybe_colon_or_space(": "), Ok("")); + assert_eq!(maybe_colon_or_space(" : "), Ok("")); + assert_eq!(maybe_colon_or_space(" : "), Ok(" ")); + assert_eq!(maybe_colon_or_space(" :"), Ok(" :")); + assert_eq!(maybe_colon_or_space(" : "), Ok(" : ")); + assert_eq!(maybe_colon_or_space(" :: "), Ok(": ")); + assert_eq!(maybe_colon_or_space(" : : "), Ok(": ")); + assert_eq!(maybe_colon_or_space("šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space("šŸ˜øšŸ˜ø"), Ok("šŸ˜øšŸ˜ø")); + assert_eq!(maybe_colon_or_space("šŸ˜ø:"), Ok("šŸ˜ø:")); + assert_eq!(maybe_colon_or_space("šŸ˜ø "), Ok("šŸ˜ø ")); + assert_eq!(maybe_colon_or_space(" šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space(":šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space(":šŸ˜ø "), Ok("šŸ˜ø ")); + assert_eq!(maybe_colon_or_space(" :šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space(" :šŸ˜ø "), Ok("šŸ˜ø ")); + assert_eq!(maybe_colon_or_space(" :šŸ˜ø:"), Ok("šŸ˜ø:")); + assert_eq!(maybe_colon_or_space(": šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space(": šŸ˜ø"), Ok(" šŸ˜ø")); + assert_eq!(maybe_colon_or_space(": :šŸ˜ø"), Ok(":šŸ˜ø")); + assert_eq!(maybe_colon_or_space(" : šŸ˜ø"), Ok("šŸ˜ø")); + assert_eq!(maybe_colon_or_space(" ::šŸ˜ø"), Ok(":šŸ˜ø")); + assert_eq!(maybe_colon_or_space(" :: šŸ˜ø"), Ok(": šŸ˜ø")); +}