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 Sep 1, 2022
1 parent 623b42a commit edb7eb5
Show file tree
Hide file tree
Showing 11 changed files with 879 additions and 130 deletions.
5 changes: 5 additions & 0 deletions src/datetime/mod.rs
Expand Up @@ -510,13 +510,18 @@ impl DateTime<FixedOffset> {
/// 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};
/// assert_eq!(
/// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(),
/// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)
/// );
/// ```
///
/// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)];
let mut parsed = Parsed::new();
Expand Down
321 changes: 297 additions & 24 deletions src/datetime/tests.rs
Expand Up @@ -86,30 +86,20 @@ fn test_datetime_with_timezone() {
}

#[test]
fn test_datetime_rfc2822_and_rfc3339() {
fn test_datetime_rfc2822() {
let edt = FixedOffset::east(5 * 60 * 60);
assert_eq!(
Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(),
"Wed, 18 Feb 2015 23:16:09 +0000"
);
assert_eq!(Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00");
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(),
"Wed, 18 Feb 2015 23:16:09 +0500"
);
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(),
"2015-02-18T23:16:09.150+05:00"
);
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(),
"Wed, 18 Feb 2015 23:59:60 +0500"
);
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-18T23:59:60.234567+05:00"
);

assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"),
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9))
Expand All @@ -119,18 +109,69 @@ fn test_datetime_rfc2822_and_rfc3339() {
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9))
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"),
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9))
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
Ok(edt.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000))
);
assert_eq!(
DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
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(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000))
);

assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err());
assert!(DateTime::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000").is_err());
assert!(DateTime::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000").is_err());
assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err());
assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err());
// note that trailing space causes failure
assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err());
// XXX: example from RFC 2822 Appendix A.5. wrongly fails due to trailing " (Newfoundland Time)"
//assert!(DateTime::parse_from_rfc2822("Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)").is_err());
}

#[test]
fn test_datetime_rfc3339() {
let edt = FixedOffset::east(5 * 60 * 60);
assert_eq!(Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00");
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(),
"2015-02-18T23:16:09.150+05:00"
);
assert_eq!(
edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).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(2015, 2, 18).and_hms_micro(23, 59, 59, 123_000))
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"),
Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 123_456))
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"),
Ok(edt.ymd(2015, 2, 18).and_hms_nano(23, 59, 59, 123_456_789))
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"),
Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567))
DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"),
Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9))
);

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]
Expand Down Expand Up @@ -204,19 +245,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

0 comments on commit edb7eb5

Please sign in to comment.