diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index d14fdd68fd..640c0faa4d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -244,6 +244,94 @@ fn test_datetime_sub_months() { ); } +// local helper function to easily create a DateTime +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(TimeDelta::milliseconds(milli)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(TimeDelta::microseconds(micro)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(TimeDelta::nanoseconds(nano)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(TimeDelta::milliseconds(milli)) + .unwrap() +} + #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -356,10 +444,6 @@ fn test_datetime_rfc2822() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - assert_eq!( - 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( @@ -372,17 +456,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); // seconds 60 assert_eq!( edt.from_local_datetime( @@ -395,17 +468,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); assert_eq!( DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), @@ -416,36 +478,35 @@ fn test_datetime_rfc2822() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 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_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" + ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), + "Thu, 19 Feb 2015 00:20:32 +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()) + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) ); // 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" + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" ), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) ); // 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() + Ok( + ymdhms( + &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), + 1969, 2, 13, 23, 32, 0, + ) ) ); // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" @@ -453,12 +514,9 @@ fn test_datetime_rfc2822() { 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()) + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) ); // bad year @@ -483,68 +541,72 @@ fn test_datetime_rfc2822() { #[test] fn test_datetime_rfc3339() { - let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_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.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00: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" + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.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()) + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) ); 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()) + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) ); 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()) + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) ); 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()) + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); 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" + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).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()) + DateTime::::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) ); 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()) + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -556,7 +618,7 @@ fn test_datetime_rfc3339() { assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -565,10 +627,7 @@ fn test_datetime_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_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert!( DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() @@ -752,18 +811,6 @@ fn test_parse_datetime_utc() { "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", @@ -822,9 +869,21 @@ fn test_parse_datetime_utc() { "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 + "2012 -12-12T12:12:12Z", // space after year + "2012 -12-12T12:12:12Z", // multi space after year + "2012- 12-12T12:12:12Z", // space after year divider + "2012- 12-12T12:12:12Z", // multi space after year divider + "2012-12-12T 12:12:12Z", // space after date-time divider + "2012-12-12T12 :12:12Z", // space after hour + "2012-12-12T12 :12:12Z", // multi space after hour + "2012-12-12T12: 12:12Z", // space before minute + "2012-12-12T12: 12:12Z", // multi space before minute + "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 " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format ]; - for &s in &invalid { + for &s in invalid.iter() { eprintln!("test_parse_datetime_utc invalid {:?}", s); assert!(s.parse::>().is_err()); } @@ -832,12 +891,12 @@ fn test_parse_datetime_utc() { #[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() - }; + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); assert_eq!( DateTime::::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::::parse_from_str( @@ -852,53 +911,38 @@ fn test_utc_datetime_from_str() { 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()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), ); 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()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); 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()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -907,7 +951,7 @@ fn test_utc_datetime_from_str() { #[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(); + let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); // 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),); @@ -923,12 +967,23 @@ fn test_utc_datetime_from_str_with_spaces() { 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 whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err()); + // trailing whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); + // extra mid-string whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // mismatched leading whitespace + assert!(Utc.datetime_from_str("\tAug 09 2013 23:54:35", "\n%b %d %Y %H:%M:%S").is_err()); + // mismatched trailing whitespace + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err()); + // mismatched mid-string whitespace + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err()); + // trailing whitespace in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err()); + // trailing whitespace (newline) in format + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); // 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 @@ -943,12 +998,7 @@ fn test_utc_datetime_from_str_with_spaces() { #[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(); + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); // timezone variations @@ -1032,13 +1082,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\n", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); // trailing colon assert!(DateTime::::parse_from_str( "Aug 09 2013 23:54:35 -09:00:", @@ -1398,13 +1446,11 @@ fn test_datetime_parse_from_str() { ), 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!(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", @@ -1419,20 +1465,16 @@ fn test_datetime_parse_from_str() { ), 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!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); assert_eq!( DateTime::::parse_from_str( "Aug 09 2013 -090023:54:35", diff --git a/src/format/mod.rs b/src/format/mod.rs index 51ababdb29..3985215dd1 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -207,27 +207,27 @@ pub enum Fixed { /// /// It does not support parsing, its use in the parser is an immediate failure. TimezoneName, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `+00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00:00` to `+24:00:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetDoubleColon, /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24` to `+24`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetTripleColon, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `Z`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, + /// In the parser, the colon may be omitted, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. diff --git a/src/format/parse.rs b/src/format/parse.rs index 388b56e709..ba0f00129c 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -42,6 +42,10 @@ fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<() }) } +/// Parse an RFC 2822 format datetime +/// e.g. `Fri, 21 Nov 1997 09:55:06 -0600` +/// +/// This function allows arbitrary intermixed whitespace per RFC 2822 appendix A.5 fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ @@ -237,7 +241,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st /// /// - Padding-agnostic (for numeric items). /// The [`Pad`](./enum.Pad.html) field is completely ignored, -/// so one can prepend any number of whitespace then any number of zeroes before numbers. +/// so one can prepend any number of zeroes before numbers. /// /// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()> @@ -292,13 +296,37 @@ where s = &s[prefix.len()..]; } - Item::Space(_) => { - s = s.trim_left(); + Item::Space(item_space) => { + for expect in item_space.chars() { + let actual = match s.chars().next() { + Some(c) => c, + None => { + return Err((s, TOO_SHORT)); + } + }; + if expect != actual { + return Err((s, INVALID)); + } + // advance `s` forward 1 char + s = scan::s_next(s); + } } #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedSpace(_) => { - s = s.trim_left(); + Item::OwnedSpace(ref item_space) => { + for expect in item_space.chars() { + let actual = match s.chars().next() { + Some(c) => c, + None => { + return Err((s, TOO_SHORT)); + } + }; + if expect != actual { + return Err((s, INVALID)); + } + // advance `s` forward 1 char + s = scan::s_next(s); + } } Item::Numeric(ref spec, ref _pad) => { @@ -331,7 +359,6 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -424,27 +451,24 @@ where | &TimezoneOffsetDoubleColon | &TimezoneOffsetTripleColon | &TimezoneOffset => { - let offset = try_consume!(scan::timezone_offset( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = try_consume!(scan::timezone_offset(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = + try_consume!(scan::timezone_offset_zulu(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } + &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), - scan::colon_or_space - )); + s = scan::trim1(s); + let offset = + try_consume!(scan::timezone_offset_permissive(s, scan::colon_or_space)); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } @@ -468,15 +492,13 @@ where } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. /// -/// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2000-01-02T03:04:05Z".parse::>(); +/// "2000-01-02 03:04:05Z".parse::>(); /// ``` impl str::FromStr for DateTime { type Err = ParseError; @@ -484,25 +506,19 @@ impl str::FromStr for DateTime { fn from_str(s: &str) -> ParseResult> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -524,7 +540,6 @@ impl str::FromStr for DateTime { #[cfg(test)] #[test] fn test_parse() { - use super::IMPOSSIBLE; use super::*; // workaround for Rust issue #22255 @@ -558,19 +573,34 @@ fn test_parse() { // whitespaces check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); check!(" ", [sp!(" ")]; ); check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!("")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!("", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" ")]; ); check!(" ", [sp!(" "), sp!(" "), sp!(" ")]; ); + check!("\t", [sp!("")]; TOO_LONG); + check!(" \n\r \n", [sp!("")]; TOO_LONG); check!("\t", [sp!("\t")]; ); + check!("\t", [sp!(" ")]; INVALID); + check!(" ", [sp!("\t")]; INVALID); check!("\t\r", [sp!("\t\r")]; ); check!("\t\r ", [sp!("\t\r ")]; ); + check!("\t \r", [sp!("\t \r")]; ); + check!(" \t\r", [sp!(" \t\r")]; ); check!(" \n\r \n", [sp!(" \n\r \n")]; ); + check!(" \t\n", [sp!(" \t")]; TOO_LONG); + check!(" \n\t", [sp!(" \t\n")]; INVALID); check!("\u{2002}", [sp!("\u{2002}")]; ); // most unicode whitespace characters check!( @@ -586,16 +616,13 @@ fn test_parse() { ]; ); 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!("a", [sp!(" ")]; INVALID); + // a Space containing a literal can match a literal, but this should not be done + check!("a", [sp!("a")]; ); check!("abc", [sp!("")]; TOO_LONG); - check!(" ", [sp!(" ")]; ); - check!(" \t\n", [sp!(" \t")]; ); - check!("", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" ")]; ); - check!(" ", [sp!(" "), sp!(" ")]; ); + check!("abc", [sp!(" ")]; INVALID); + check!(" abc", [sp!("")]; TOO_LONG); + check!(" abc", [sp!(" ")]; TOO_LONG); // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" @@ -636,7 +663,7 @@ fn test_parse() { // 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")]; INVALID); check!("x y", [lit!("x"), sp!(" "), lit!("y")]; ); // whitespaces + literals @@ -657,7 +684,7 @@ fn test_parse() { 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", [num!(Year)]; INVALID); check!(" \t987", [sp!(" \t"), num!(Year)]; year: 987); check!(" \t987🤠", [sp!(" \t"), num!(Year), lit!("🤠")]; year: 987); check!("987🤠", [num!(Year), lit!("🤠")]; year: 987); @@ -669,9 +696,9 @@ fn test_parse() { check!("12345", [nums!(Year), lit!("5")]; year: 1234); 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), num!(Year)]; INVALID); check!("1234 1234", [num!(Year), sp!(" "), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); + check!("1234 1235", [num!(Year), num!(Year)]; INVALID); check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); @@ -687,8 +714,10 @@ fn test_parse() { check!("+0042", [num!(Year)]; year: 42); check!("-42195", [num!(Year)]; year: -42195); check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); + check!(" -42195", [num!(Year)]; INVALID); + check!(" +42195", [num!(Year)]; INVALID); + check!(" -42195", [num!(Year)]; INVALID); + check!(" +42195", [num!(Year)]; INVALID); check!("-42195 ", [num!(Year)]; TOO_LONG); check!("+42195 ", [num!(Year)]; TOO_LONG); check!(" - 42", [num!(Year)]; INVALID); @@ -704,7 +733,8 @@ fn test_parse() { check!("345", [num!(Ordinal)]; ordinal: 345); check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!(" 345", [num!(Ordinal)]; INVALID); + check!("345 ", [num!(Ordinal)]; TOO_LONG); check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); check!("345🤠 ", [num!(Ordinal), lit!("🤠"), sp!(" ")]; ordinal: 345); @@ -717,21 +747,27 @@ fn test_parse() { check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields + check!("1234 5678", [num!(Year), num!(IsoYear)]; INVALID); check!("1234 5678", - [num!(Year), num!(IsoYear)]; + [num!(Year), sp!(" "), num!(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + INVALID); + check!("12 34🤠56 78", + [num!(YearDiv100), sp!(" "), num!(YearMod100), + lit!("🤠"), num!(IsoYearDiv100), sp!(" "), num!(IsoYearMod100)]; year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; + [num!(Month), sp!(" "), num!(Day), sp!(" "), num!(WeekFromSun), sp!(" "), + num!(WeekFromMon), sp!(" "), num!(IsoWeek), sp!(" "), num!(NumDaysFromSun)]; month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + [num!(WeekdayFromMon), sp!(" "), num!(Ordinal), sp!(" "), num!(Hour12)]; weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + [num!(Hour), sp!(" "), num!(Minute), sp!(" "), num!(Second), sp!(" "), + num!(Nanosecond), sp!(" "), num!(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); @@ -920,9 +956,9 @@ fn test_parse() { 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)]; INVALID); + check!(" -12:34", [fix!(TimezoneOffset)]; INVALID); + check!("\t -12:34", [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); @@ -1015,7 +1051,7 @@ fn test_parse() { 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!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); diff --git a/src/format/scan.rs b/src/format/scan.rs index 0b74eba96a..28c647a7a5 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -198,6 +198,27 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } } +/// Returns slice remaining after first char. +/// If <=1 chars in `s` then return an empty slice +pub(super) fn s_next(s: &str) -> &str { + match s.char_indices().nth(1) { + Some((offset, _)) => &s[offset..], + None => { + // one or zero chars in `s`, return empty string + &s[s.len()..] + } + } +} + +/// If the first `char` is whitespace then consume it and return `s`. +/// Else return `s`. +pub(super) fn trim1(s: &str) -> &str { + match s.chars().next() { + Some(c) if c.is_whitespace() => s_next(s), + Some(_) | None => s, + } +} + /// 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())) @@ -405,3 +426,41 @@ fn test_rfc2822_comments() { ); } } + +#[test] +fn test_space() { + assert_eq!(space(""), Err(TOO_SHORT)); + assert_eq!(space(" "), Ok("")); + assert_eq!(space(" \t"), Ok("")); + assert_eq!(space(" \ta"), Ok("a")); + assert_eq!(space(" \ta "), Ok("a ")); + assert_eq!(space("a"), Err(INVALID)); + assert_eq!(space("a "), Err(INVALID)); +} + +#[test] +fn test_s_next() { + assert_eq!(s_next(""), ""); + assert_eq!(s_next(" "), ""); + assert_eq!(s_next("a"), ""); + assert_eq!(s_next("ab"), "b"); + assert_eq!(s_next("abc"), "bc"); + assert_eq!(s_next("😾b"), "b"); + assert_eq!(s_next("a😾"), "😾"); + assert_eq!(s_next("😾bc"), "bc"); + assert_eq!(s_next("a😾c"), "😾c"); +} + +#[test] +fn test_trim1() { + assert_eq!(trim1(""), ""); + assert_eq!(trim1(" "), ""); + assert_eq!(trim1("\t"), ""); + assert_eq!(trim1("\t\t"), "\t"); + assert_eq!(trim1(" "), " "); + assert_eq!(trim1("a"), "a"); + assert_eq!(trim1("a "), "a "); + assert_eq!(trim1("ab"), "ab"); + assert_eq!(trim1("😼"), "😼"); + assert_eq!(trim1("😼b"), "😼b"); +} diff --git a/src/naive/date.rs b/src/naive/date.rs index da2c63b28f..92739401a4 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1960,13 +1960,10 @@ impl str::FromStr for NaiveDate { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -2735,14 +2732,14 @@ mod tests { // valid cases let valid = [ "-0000000123456-1-2", - " -123456 - 1 - 2 ", + "-123456-1-2", "-12345-1-2", "-1234-12-31", "-7-6-5", "350-2-28", "360-02-29", "0360-02-29", - "2015-2 -18", + "2015-2-18", "2015-02-18", "+70-2-18", "+70000-2-18", @@ -2778,21 +2775,30 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error 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) + "", // 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 + "2001 -02-03", // space after year + "2001- 02-03", // space before month + "2001 - 02-03", // space around year-month divider + "2001-02 -03", // space after month + "2001-02- 03", // space before day + "2001-02 - 03", // space around month-day divider + "2001-02-03 ", // trailing space + " 2001-02-03", // leading space + " -123456 - 1 - 2 ", // many spaces + "9999999-9-9", // invalid year (out of bounds) ]; for &s in &invalid { eprintln!("test_date_from_str invalid {:?}", s); @@ -2808,7 +2814,7 @@ mod tests { Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), + NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u=%Y-%j"), Ok(ymd(2015, 2, 2)) ); assert_eq!( diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 963c239b6a..c07c00f659 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -203,7 +203,6 @@ fn test_datetime_from_str() { "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); @@ -232,9 +231,9 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error let invalid = [ - "", // empty - "x", // invalid / missing data - "15", // missing data + "", // 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 @@ -249,13 +248,27 @@ fn test_datetime_from_str() { "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", // space after year + "2012 -12-12T12:12:12", // multi space after year + "2012- 12-12T12:12:12", // space before month + "2012- 12-12T12:12:12", // multi space before month + "2012-12-12 T12:12:12", // space after day + "2012-12-12T 12:12:12", // space after date-time divider + "2012-12-12T12 :12:12", // space after hour + "2012-12-12T12 :12:12", // multi space after hour + "2012-12-12T12: 12:12", // space before minute + "2012-12-12T12: 12:12", // multi space before minute + "2012-12-12T12 : 12:12", // space around hour-minute divider + "2012-12-12T12:12:12 ", // trailing space + " 2012-12-12T12:12:12", // leading space "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 + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", // many spaces ]; - for &s in &invalid { + for &s in invalid.iter() { eprintln!("test_datetime_from_str invalid {:?}", s); assert!(s.parse::().is_err()); } @@ -272,8 +285,12 @@ fn test_datetime_parse_from_str() { NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56)) ); // ignore offset + assert!( + // intermixed whitespace + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S").is_err() + ); assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u %H%M%S"), Ok(ymdhms(2015, 2, 2, 0, 0, 0)) ); assert_eq!( diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f0f69d485e..2b7204f34a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1257,14 +1257,11 @@ impl str::FromStr for NaiveTime { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), ]; let mut parsed = Parsed::new(); diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 8f5f3f115f..4aba43434b 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -232,9 +232,6 @@ fn test_date_from_str() { "0:0:0", "0:0:0.0000000", "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", "01:02:03", "4:3:2.1", "9:8:7", @@ -303,6 +300,18 @@ fn test_date_from_str() { "1441497364.649", // valid datetime, not a NaiveTime "+1441497364.649", // valid datetime, not a NaiveTime "+1441497364", // valid datetime, not a NaiveTime + "01 :02:03", // space after hour + "01: 02:03", // space before minute + "01 : 02:03", // space around hour-minute divider + "01:02 :03", // space after minute + "01:02: 03", // space before second + "01:02 : 03", // space around minute-second divider + "01:02:03 .456", // space after second + "01:02:03. 456", // space before fraction + "01:02:03 ", // trailing space + "01:02:03.456 ", // trailing space + " 01:02:03", // leading space + " 4 : 3 : 2.1 ", // spaces intermixed throughout "001:02:03", // invalid hour "01:002:03", // invalid minute "01:02:003", // invalid second @@ -330,9 +339,9 @@ fn test_time_parse_from_str() { 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:59 PM", "%H:%M\t%P").is_err()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_err()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_err()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); }