Skip to content

Commit

Permalink
be exact about whitespace
Browse files Browse the repository at this point in the history
Be exact about whitespace in parsing. This changes
pattern matching in `format::parse::parse` as it does not allow
arbitrary whitespace before, after, or between the datetime specifiers.

`format/parse.rs:datetime_from_str` is exact about whitespace in
the passed data `s` and passed strftime format `fmt`.

Also be more exacting about colons and whitespace around timezones.
Instead of unlimited colons and whitespace, only match a more limited
possible set of leading colons and whitespace.

Issue #660
  • Loading branch information
jtmoon79 committed Aug 31, 2022
1 parent 623b42a commit a37d1e0
Show file tree
Hide file tree
Showing 10 changed files with 728 additions and 113 deletions.
248 changes: 240 additions & 8 deletions src/datetime/tests.rs
Expand Up @@ -204,19 +204,251 @@ fn test_fixedoffset_parse_from_str() {
// no test for `DateTime<Local>`, we cannot verify that much.
}

#[test]
fn test_datetime_from_str() {
// with varying spaces - should succeed
assert_eq!(
Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
assert_eq!(
Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)),
);
// with varying spaces - should fail
assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err());
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err());
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 ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s);
let dt = FixedOffset::east(-9 * 60 * 60).ymd(2013, 8, 9).and_hms(23, 54, 35);

// timezone variations

// %Z
assert!(DateTime::parse_from_str("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err());
assert!(DateTime::parse_from_str("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err());
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 --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),
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z ").is_err()
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()
);
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 -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!(DateTime::parse_from_str("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err());
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()
);
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!(DateTime::parse_from_str("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z")
.is_err());
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("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60))
); // ignore offset
assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset
assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT")
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),
);
assert!(DateTime::parse_from_str("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::")
.is_err());
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),
);
assert!(DateTime::parse_from_str("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S")
.is_err());
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),
);
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()
);
// %::::z
assert!(
DateTime::parse_from_str("Aug 09 2013 23:54:35 -0900", "%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!(DateTime::parse_from_str("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err());
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!(
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", "%b %d %Y %H:%M:%S+%#z"),
Ok(dt),
);
assert_eq!(
Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"),
Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35))
DateTime::parse_from_str("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"),
Ok(dt),
);
}

Expand Down
14 changes: 7 additions & 7 deletions src/format/mod.rs
Expand Up @@ -218,27 +218,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`).
/// Offset from the local time to UTC with seconds (`+09:00:00` or `-0400:00` or `+000000`).
///
/// 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.
Expand Down

0 comments on commit a37d1e0

Please sign in to comment.