Skip to content

Commit

Permalink
Fix parsing for zone offset seconds
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Dec 11, 2024
1 parent 8b86349 commit db464fa
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 14 deletions.
18 changes: 9 additions & 9 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1212,28 +1212,28 @@ fn test_datetime_parse_from_str() {
assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
// mismatching colon expectations
assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err());
assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt));
// wrong timezone data
assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err());
assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -0900001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09:00001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt));
assert!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:").is_err());
assert!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0").is_err());
// mismatching colons and spaces
assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err());
// mismatching colons expectations
assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err());
assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt));
assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt));
assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt));
assert!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S").is_err());
assert!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S").is_err());
// mismatching colons expectations mid-string
assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err());
// mismatching colons expectations, before end
assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err());
assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt));

//
// %:::z
Expand Down
13 changes: 11 additions & 2 deletions src/format/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes
parsed.set_nanosecond(nanosecond)?;
}

let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true));
let offset =
try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true, false));
// This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant.
// But it is possible to read the offset directly from `Parsed`. We want to only successfully
// populate `Parsed` if the input is fully valid RFC 3339.
Expand Down Expand Up @@ -461,6 +462,7 @@ where
false,
false,
true,
matches!(spec, &TimezoneOffsetDoubleColon),
));
parsed.set_offset(i64::from(offset))?;
}
Expand All @@ -472,6 +474,7 @@ where
true,
false,
true,
false,
));
parsed.set_offset(i64::from(offset))?;
}
Expand All @@ -484,6 +487,7 @@ where
true,
true,
true,
false,
));
parsed.set_offset(i64::from(offset))?;
}
Expand Down Expand Up @@ -576,7 +580,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult
let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) {
(&s[3..], 0)
} else {
scan::timezone_offset(s, scan::colon_or_space, true, false, true)?
scan::timezone_offset(s, scan::colon_or_space, true, false, true, false)?
};
parsed.set_offset(i64::from(offset))?;
Ok((s, ()))
Expand Down Expand Up @@ -1833,6 +1837,11 @@ mod tests {
}
}

#[test]
fn issue_1629() {
DateTime::parse_from_str("2023-01-02T23:24:25+01:30:01", "%Y-%m-%dT%H:%M:%S%::z").unwrap();
}

#[test]
fn test_issue_1010() {
let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}",
Expand Down
24 changes: 22 additions & 2 deletions src/format/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ pub(crate) fn timezone_offset<F>(
allow_zulu: bool,
allow_missing_minutes: bool,
allow_tz_minus_sign: bool,
allow_seconds: bool,
) -> ParseResult<(&str, i32)>
where
F: FnMut(&str) -> ParseResult<&str>,
Expand Down Expand Up @@ -272,13 +273,32 @@ where
} else {
return Err(TOO_SHORT);
};

s = match s.len() {
len if len >= 2 => &s[2..],
0 => s,
_ => return Err(TOO_SHORT),
};

let mut seconds = hours * 3600 + minutes * 60;
match s.chars().next() {
Some(':' | '0'..='9') if allow_seconds => {}
_ => return Ok((s, if negative { -seconds } else { seconds })),
}

s = consume_colon(s)?;
seconds += match digits(s) {
Ok((s1 @ b'0'..=b'5', s2 @ b'0'..=b'9')) => i32::from((s1 - b'0') * 10 + (s2 - b'0')),
Ok((b'6'..=b'9', b'0'..=b'9')) => return Err(OUT_OF_RANGE),

Check warning on line 292 in src/format/scan.rs

View check run for this annotation

Codecov / codecov/patch

src/format/scan.rs#L292

Added line #L292 was not covered by tests
_ => return Err(INVALID),
};

s = match s.len() {
len if len >= 2 => &s[2..],
0 => s,
_ => return Err(TOO_SHORT),
};

let seconds = hours * 3600 + minutes * 60;
Ok((s, if negative { -seconds } else { seconds }))
}

Expand Down Expand Up @@ -319,7 +339,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> {
}
Err(INVALID)
} else {
timezone_offset(s, |s| Ok(s), false, false, false)
timezone_offset(s, |s| Ok(s), false, false, false, false)
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/offset/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ impl FixedOffset {
impl FromStr for FixedOffset {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?;
let (_, offset) =
scan::timezone_offset(s, scan::colon_or_space, false, false, true, false)?;
Self::east_opt(offset).ok_or(OUT_OF_RANGE)
}
}
Expand Down

0 comments on commit db464fa

Please sign in to comment.