From e1400f24f767ee6dfddf11f10a53659f4d4416bb Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:25:18 -0700 Subject: [PATCH] Add various tests for current parsing Add more varying testing for most parsing functions. Tests emphasize whitespace, literals, timezones, and timezone delimiters (colons and whitespace). Add tests for multiple-byte characters and combining characters in and around data and parsing formats. These tests are added to aid humans verifying the next commit that changes parsing behavior. Issue #660 --- src/datetime/mod.rs | 5 + src/datetime/tests.rs | 897 +++++++++++++++++++++++++++++++++++- src/format/parse.rs | 603 +++++++++++++++++++++--- src/format/strftime.rs | 82 ++++ src/naive/date.rs | 33 +- src/naive/datetime/tests.rs | 45 +- src/naive/time/tests.rs | 70 ++- 7 files changed, 1653 insertions(+), 82 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e05625c31f..06d7852e95 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -550,6 +550,9 @@ impl DateTime { /// RFC 2822 is the internet message standard that specifies the /// representation of times in HTTP and email headers. /// + /// The RFC 2822 standard allows arbitrary intermixed whitespace. + /// See [RFC 2822 Appendix A.5] + /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; /// assert_eq!( @@ -557,6 +560,8 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` + /// + /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 0f03fca57c..c212ac71fb 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -344,8 +344,10 @@ fn test_datetime_with_timezone() { } #[test] -fn test_datetime_rfc2822_and_rfc3339() { +fn test_datetime_rfc2822() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 assert_eq!( Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" @@ -354,6 +356,7 @@ fn test_datetime_rfc2822_and_rfc3339() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -376,6 +379,7 @@ fn test_datetime_rfc2822_and_rfc3339() { .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + // seconds 60 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -408,7 +412,130 @@ fn test_datetime_rfc2822_and_rfc3339() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + ), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap() + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap()) + ); + + // bad year + assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() + ); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") + .is_err()); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") + .is_err()); + // wrong day of week separator '.' + assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() + ); +} + +#[test] +fn test_datetime_rfc3339() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) + ); + + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( @@ -434,6 +561,44 @@ fn test_datetime_rfc2822_and_rfc3339() { ) .unwrap()) ); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() + ); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() + ); } #[test] @@ -575,7 +740,94 @@ fn test_datetime_from_str() { } #[test] -fn test_datetime_parse_from_str() { +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {:?}", s); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12t12:12:12Z", // wrong divider 't' + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {:?}", s); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_utc_datetime_from_str() { let ymdhms = |y, m, d, h, n, s, off| { FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() }; @@ -590,6 +842,645 @@ fn test_datetime_parse_from_str() { Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap()) ); + + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_utc_datetime_from_str_with_spaces() { + let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!( + Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), + Ok(dt), + ); + assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + // with varying spaces - should fail + // leading space in data + assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = FixedOffset::east_opt(-9 * 60 * 60) + .unwrap() + .ymd_opt(2013, 8, 9) + .unwrap() + .and_hms_opt(23, 54, 35) + .unwrap(); + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data? + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 PST", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 XXXXX", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + + // + // %z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 --0900", + "%b %d %Y %H:%M:%S -%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 +-0900", + "%b %d %Y %H:%M:%S +%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing newline after timezone + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing colon + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + // trailing colon with space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00: ", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // trailing colon, mismatch space + 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", + "%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_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900::", + "%b %d %Y %H:%M:%S %z::" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %z:00" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %z:00 " + ), + Ok(dt), + ); + + // + // %:z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00:", + "%b %d %Y %H:%M:%S %:z:" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%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), + ); + // timezone data hs too many colons + 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::", + "%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), + ); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // mismatching colon expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%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), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %::z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900\t\n", + "%b %d %Y %H:%M:%S %::z\t\n" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900:", + "%b %d %Y %H:%M:%S %::z:" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900:0", + "%b %d %Y %H:%M:%S :%::z:0" + ), + Ok(dt), + ); + // mismatching colons and spaces + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900: ", + "%b %d %Y %H:%M:%S :%::z::" + ) + .is_err()); + // mismatching colons expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900: 23:54:35", + "%b %d %Y %::z: %H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 :-0900:0 23:54:35", + "%b %d %Y :%::z:0 %H:%M:%S" + ), + Ok(dt), + ); + // mismatching colons expectations mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 :-0900: 23:54:35", + "%b %d %Y :%::z %H:%M:%S" + ) + .is_err()); + // mismatching colons expectations, before end + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %::z " + ) + .is_err()); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %:::z " + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:::z" + ) + .is_err()); + + // + // %::::z + // + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + + // + // %#z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09: ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35+-09", + "%b %d %Y %H:%M:%S+%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35--09", + "%b %d %Y %H:%M:%S-%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -090023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:0023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + // timezone with partial minutes adjacent hours + assert_ne!( + DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), + Ok(dt), + ); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090 ", + "%b %d %Y %H:%M:%S %#z " + ) + .is_err()); + // bad timezone data (partial minutes) mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -090 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:00 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // timezone data ambiguous with hours + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); } #[test] diff --git a/src/format/parse.rs b/src/format/parse.rs index e894f710b9..7d4f56656b 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -536,9 +536,11 @@ fn test_parse() { macro_rules! check { ($fmt:expr, $items:expr; $err:tt) => ( + eprintln!("test_parse: format {:?}", $fmt); assert_eq!(parse_all($fmt, &$items), Err($err)) ); ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + eprintln!("test_parse: format {:?}", $fmt); let mut expected = Parsed::new(); $(expected.$k = Some($v);)* assert_eq!(parse_all($fmt, &$items), Ok(expected)) @@ -549,35 +551,114 @@ fn test_parse() { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); + check!("abc", []; TOO_LONG); + check!("🀠", []; TOO_LONG); // whitespaces check!("", [sp!("")]; ); check!(" ", [sp!("")]; ); check!("\t", [sp!("")]; ); check!(" \n\r \n", [sp!("")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" "), sp!(" ")]; ); + check!("\t", [sp!("\t")]; ); + check!("\t\r", [sp!("\t\r")]; ); + check!("\t\r ", [sp!("\t\r ")]; ); + check!(" \n\r \n", [sp!(" \n\r \n")]; ); + check!("\u{2002}", [sp!("\u{2002}")]; ); + // most unicode whitespace characters + check!( + "\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}", + [sp!("\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}")]; + ); + // most unicode whitespace characters + check!( + "\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}", + [ + sp!("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + sp!("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") + ]; + ); check!("a", [sp!("")]; TOO_LONG); + check!("a", [sp!(" ")]; TOO_LONG); + // a Space containing a literal cannot match a literal + check!("a", [sp!("a")]; TOO_LONG); + check!("abc", [sp!("")]; TOO_LONG); + check!(" ", [sp!(" ")]; ); + check!(" \t\n", [sp!(" \t")]; ); + check!("", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal + check!("", [lit!("")]; ); check!("", [lit!("a")]; TOO_SHORT); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); + check!("🀠", [lit!("a")]; INVALID); check!("A", [lit!("a")]; INVALID); + check!("a", [lit!("z")]; INVALID); + check!("a", [lit!("🀠")]; TOO_SHORT); + check!("a", [lit!("\u{0363}a")]; TOO_SHORT); + check!("\u{0363}a", [lit!("a")]; INVALID); + check!("\u{0363}a", [lit!("\u{0363}a")]; ); + check!("a", [lit!("ab")]; TOO_SHORT); + check!("xy", [lit!("xy")]; ); + check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("PST", [lit!("PST")]; ); + check!("🀠", [lit!("🀠")]; ); + check!("🀠a", [lit!("🀠"), lit!("a")]; ); + check!("🀠a🀠", [lit!("🀠"), lit!("a🀠")]; ); + check!("a🀠b", [lit!("a"), lit!("🀠"), lit!("b")]; ); + // literals can be together check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [lit!("x"), sp!(" "), lit!("y")]; ); + + // whitespaces + literals + check!("a\n", [lit!("a"), sp!("\n")]; ); + check!("\tab\n", [sp!("\t"), lit!("ab"), sp!("\n")]; ); + check!("ab\tcd\ne", [lit!("ab"), sp!("\t"), lit!("cd"), sp!("\n"), lit!("e")]; ); + check!("+1ab\tcd\r\n+,.", [lit!("+1ab"), sp!("\t"), lit!("cd"), sp!("\r\n"), lit!("+,.")]; ); + // whitespace and literals can be intermixed + check!("a\tb", [lit!("a\tb")]; ); + check!("a\tb", [lit!("a"), sp!("\t"), lit!("b")]; ); // numeric check!("1987", [num!(Year)]; year: 1987); check!("1987 ", [num!(Year)]; TOO_LONG); check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed check!("x123", [num!(Year)]; INVALID); + check!("o123", [num!(Year)]; INVALID); check!("2015", [num!(Year)]; year: 2015); check!("0000", [num!(Year)]; year: 0); check!("9999", [num!(Year)]; year: 9999); check!(" \t987", [num!(Year)]; year: 987); + check!(" \t987", [sp!(" \t"), num!(Year)]; year: 987); + check!(" \t987🀠", [sp!(" \t"), num!(Year), lit!("🀠")]; year: 987); + check!("987🀠", [num!(Year), lit!("🀠")]; year: 987); check!("5", [num!(Year)]; year: 5); check!("5\0", [num!(Year)]; TOO_LONG); check!("\x005", [num!(Year)]; INVALID); @@ -587,11 +668,15 @@ fn test_parse() { check!("12345", [num0!(Year), lit!("5")]; year: 1234); check!("12341234", [num!(Year), num!(Year)]; year: 1234); check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1234", [num!(Year), sp!(" "), num!(Year)]; year: 1234); check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234xx1234", [num!(Year), lit!("xx"), num!(Year)]; year: 1234); + check!("1234 x 1234", [num!(Year), sp!(" "), lit!("x"), sp!(" "), num!(Year)]; year: 1234); + check!("1234 x 1235", [num!(Year), sp!(" "), lit!("x"), sp!(" "), lit!("1235")]; year: 1234); // signed numeric check!("-42", [num!(Year)]; year: -42); @@ -602,8 +687,14 @@ fn test_parse() { check!("+42195", [num!(Year)]; year: 42195); check!(" -42195", [num!(Year)]; year: -42195); check!(" +42195", [num!(Year)]; year: 42195); + check!("-42195 ", [num!(Year)]; TOO_LONG); + check!("+42195 ", [num!(Year)]; TOO_LONG); check!(" - 42", [num!(Year)]; INVALID); check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); check!("-", [num!(Year)]; TOO_SHORT); check!("+", [num!(Year)]; TOO_SHORT); @@ -612,8 +703,16 @@ fn test_parse() { check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🀠 ", [num!(Ordinal), lit!("🀠"), sp!(" ")]; ordinal: 345); + check!("345🀠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); check!(" +345", [num!(Ordinal)]; INVALID); check!(" -345", [num!(Ordinal)]; INVALID); + check!("\t345", [sp!("\t"), num!(Ordinal)]; ordinal: 345); + check!(" +345", [sp!(" "), num!(Ordinal)]; INVALID); + check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields check!("1234 5678", @@ -639,6 +738,7 @@ fn test_parse() { check!("Apr", [fix!(ShortMonthName)]; month: 4); check!("APR", [fix!(ShortMonthName)]; month: 4); check!("ApR", [fix!(ShortMonthName)]; month: 4); + check!("\u{0363}APR", [fix!(ShortMonthName)]; INVALID); check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed check!("A", [fix!(ShortMonthName)]; TOO_SHORT); check!("Sol", [fix!(ShortMonthName)]; INVALID); @@ -676,7 +776,15 @@ fn test_parse() { check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [sp!(" "), fix!(LowerAmPm)]; hour_div_12: 0); + check!("Am🀠", [fix!(LowerAmPm), lit!("🀠")]; hour_div_12: 0); + check!("🀠Am", [lit!("🀠"), fix!(LowerAmPm)]; hour_div_12: 0); + check!("\u{0363}am", [fix!(LowerAmPm)]; INVALID); + check!("\u{0360}am", [fix!(LowerAmPm)]; INVALID); check!(" Am", [fix!(LowerAmPm)]; INVALID); + check!("Am ", [fix!(LowerAmPm)]; TOO_LONG); + check!("a.m.", [fix!(LowerAmPm)]; INVALID); + check!("A.M.", [fix!(LowerAmPm)]; INVALID); check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fix!(LowerAmPm)]; TOO_SHORT); check!("p", [fix!(LowerAmPm)]; TOO_SHORT); @@ -693,10 +801,21 @@ fn test_parse() { check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); + check!(".421951", [fix!(Nanosecond)]; nanosecond: 421_951_000); + check!(".4219512", [fix!(Nanosecond)]; nanosecond: 421_951_200); + check!(".42195123", [fix!(Nanosecond)]; nanosecond: 421_951_230); check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".4219508035", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".42195080354", [fix!(Nanosecond)]; nanosecond: 421_950_803); check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000031", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000035", [fix!(Nanosecond)]; nanosecond: 3); check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); + check!(".0000000009", [fix!(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); + check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); + check!(".4🀠", [fix!(Nanosecond), lit!("🀠")]; nanosecond: 400_000_000); check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(". 4", [fix!(Nanosecond)]; INVALID); @@ -708,76 +827,400 @@ fn test_parse() { check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("4210", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("421🀠", [internal_fix!(Nanosecond3NoDot), lit!("🀠")]; nanosecond: 421_000_000); + check!("🀠421", [lit!("🀠"), internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("123456789", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("1234567", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); + check!("123456789", [internal_fix!(Nanosecond6NoDot)]; TOO_LONG); check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("1234567890", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + 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: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:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [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)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -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)]; offset: -45_240); + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); + check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + 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)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + 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: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)]; 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)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+πŸ™ƒ", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + 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)]; 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)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🀠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🀠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive), lit!("🀠")]; offset: 45_240); + check!("🀠+12:34", [lit!("🀠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+πŸ™ƒ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -790,10 +1233,31 @@ fn test_parse() { num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", + check!("20150204143705.567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); + check!("20150204143705.567891", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891000); + check!("20150204143705.567891023", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891023); + check!("Mon, 10 Jun 2013 09:32:37 GMT", [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("🀠Mon, 10 Jun🀠2013 09:32:37 GMT🀠", + [lit!("🀠"), fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), + fix!(ShortMonthName), lit!("🀠"), num!(Year), sp!(" "), num!(Hour), lit!(":"), + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT"), lit!("🀠")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", @@ -814,6 +1278,22 @@ fn test_parse() { check!("12345678901234.56789", [num!(Timestamp), fix!(Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + + // docstring examples from `impl str::FromStr` + check!("2000-01-02T03:04:05Z", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); + check!("2000-01-02 03:04:05Z", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), sp!(" "), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); } #[cfg(test)] @@ -830,6 +1310,8 @@ fn test_rfc2822() { ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace + ("Tue, 20 Jan 2015\t17:35:20\t-0800\t\t(UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok("Tue, 20 Jan 2015 17:35:20 -0800"), @@ -876,6 +1358,7 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -963,11 +1446,26 @@ fn test_rfc3339() { ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { @@ -982,6 +1480,7 @@ fn test_rfc3339() { // Test against test data above for &(date, checkdate) in testdates.iter() { + eprintln!("test_rfc3339: date {:?}, expect {:?}", date, checkdate); let d = rfc3339_to_datetime(date); // parse a date let dt = match d { // did we get a value? diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 24bae20c0a..fc97da3c0f 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -513,12 +513,32 @@ impl<'a> Iterator for StrftimeItems<'a> { fn test_strftime_items() { fn parse_and_collect(s: &str) -> Vec> { // map any error into `[Item::Error]`. useful for easy testing. + eprintln!("test_strftime_items: parse_and_collect({:?})", s); let items = StrftimeItems::new(s); let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); items.collect::>>().unwrap_or_else(|| vec![Item::Error]) } assert_eq!(parse_and_collect(""), []); + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + assert_eq!(parse_and_collect("a"), [lit!("a")]); + assert_eq!(parse_and_collect("ab"), [lit!("ab")]); + assert_eq!(parse_and_collect("😽"), [lit!("😽")]); + assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]); + assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]); + assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); assert_eq!( @@ -532,12 +552,63 @@ fn test_strftime_items() { parse_and_collect("%Y-%m-%d"), [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] ); + assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); + assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]); + assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]); + assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 πŸ˜½γ―γ„πŸ˜½ ハンバーガー"), + [sp!(" "), lit!("😽"), sp!(" "), lit!("πŸ˜½γ―γ„πŸ˜½"), sp!(" "), lit!("ハンバーガー")] + ); + assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]); + assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]); assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); assert_eq!(parse_and_collect("%%"), [lit!("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]); + assert_eq!(parse_and_collect("%%a%"), [Item::Error]); + assert_eq!(parse_and_collect("%😽"), [Item::Error]); + assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); + assert_eq!(parse_and_collect("%%%%ハンバーガー"), [lit!("%"), lit!("%"), lit!("ハンバーガー")]); assert_eq!(parse_and_collect("foo%?"), [Item::Error]); assert_eq!(parse_and_collect("bar%42"), [Item::Error]); assert_eq!(parse_and_collect("quux% +"), [Item::Error]); @@ -557,6 +628,10 @@ fn test_strftime_items() { assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]); assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); assert_eq!(parse_and_collect("%#m"), [Item::Error]); } @@ -666,6 +741,13 @@ fn test_strftime_docs() { assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); + + // complex format specifiers + assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); + assert_eq!( + dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), + " 20010807%%\t00:am:3460+09\t" + ); } #[cfg(feature = "unstable-locales")] diff --git a/src/naive/date.rs b/src/naive/date.rs index c5b6fe0d74..a94985d816 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2828,16 +2828,20 @@ mod tests { "360-02-29", "0360-02-29", "2015-2 -18", + "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; + eprintln!("d {:?} (NaiveDate)", d); let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, @@ -2845,6 +2849,7 @@ mod tests { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; + eprintln!("d_ {:?} (NaiveDate)", d_); assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ @@ -2857,13 +2862,27 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 202bdb34d3..da36481f27 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -197,11 +197,16 @@ fn test_datetime_timestamp() { fn test_datetime_from_str() { // valid cases let valid = [ - "2015-2-18T23:16:9.15", + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { + eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -226,16 +231,34 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 62c46a247e..9ebf174948 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -235,9 +235,37 @@ fn test_date_from_str() { " 4 : 3 : 2.1 ", " 09:08:07 ", " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", "23:59:60.373929310237", ]; for &s in &valid { + eprintln!("test_time_parse_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -262,15 +290,30 @@ fn test_date_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8", // missing data + "15:8:x", // missing data, invalid data + "15:8:9x", // missing data, invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] @@ -281,6 +324,15 @@ fn test_time_parse_from_str() { Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); }