From 99bc2d5f1473ad05fc6ebefdc27558d20f7f266b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 2 Mar 2024 08:28:52 +0100 Subject: [PATCH] Support parsing negative timestamps --- src/format/parse.rs | 34 +++++++++++++++++----------------- src/format/scan.rs | 16 ++++++++++++---- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 097e02d81a..f89b99a493 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -109,14 +109,14 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } s = s.trim_start(); - parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; + parsed.set_day(try_consume!(scan::number(s, 1, 2, true)))?; s = scan::space(s)?; // mandatory parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; s = scan::space(s)?; // mandatory // distinguish two- and three-digit years from four-digit years let prevlen = s.len(); - let mut year = try_consume!(scan::number(s, 2, usize::MAX)); + let mut year = try_consume!(scan::number(s, 2, usize::MAX, true)); let yearlen = prevlen - s.len(); match (yearlen, year) { (2, 0..=49) => { @@ -133,12 +133,12 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_year(year)?; s = scan::space(s)?; // mandatory - parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_hour(try_consume!(scan::number(s, 2, 2, true)))?; s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S - parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_minute(try_consume!(scan::number(s, 2, 2, true)))?; if let Ok(s_) = scan::char(s.trim_start(), b':') { // [ ":" *S 2DIGIT ] - parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; + parsed.set_second(try_consume!(scan::number(s_, 2, 2, true)))?; } s = scan::space(s)?; // mandatory @@ -190,11 +190,11 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes // // - For readability a full-date and a full-time may be separated by a space character. - parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; + parsed.set_year(try_consume!(scan::number(s, 4, 4, true)))?; s = scan::char(s, b'-')?; - parsed.set_month(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_month(try_consume!(scan::number(s, 2, 2, true)))?; s = scan::char(s, b'-')?; - parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_day(try_consume!(scan::number(s, 2, 2, true)))?; s = match s.as_bytes().first() { Some(&b't' | &b'T' | &b' ') => &s[1..], @@ -202,11 +202,11 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes None => return Err(TOO_SHORT), }; - parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_hour(try_consume!(scan::number(s, 2, 2, true)))?; s = scan::char(s, b':')?; - parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_minute(try_consume!(scan::number(s, 2, 2, true)))?; s = scan::char(s, b':')?; - parsed.set_second(try_consume!(scan::number(s, 2, 2)))?; + parsed.set_second(try_consume!(scan::number(s, 2, 2, true)))?; if s.starts_with('.') { let nanosecond = try_consume!(scan::nanosecond(&s[1..])); parsed.set_nanosecond(nanosecond)?; @@ -357,7 +357,7 @@ where Minute => (2, false, Parsed::set_minute), Second => (2, false, Parsed::set_second), Nanosecond => (9, false, Parsed::set_nanosecond), - Timestamp => (usize::MAX, false, Parsed::set_timestamp), + Timestamp => (usize::MAX, true, Parsed::set_timestamp), // for the future expansion Internal(ref int) => match int._dummy {}, @@ -366,16 +366,15 @@ where s = s.trim_start(); let v = if signed { if s.starts_with('-') { - let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); - 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? + try_consume!(scan::number(&s[1..], 1, usize::MAX, false)) } else if s.starts_with('+') { - try_consume!(scan::number(&s[1..], 1, usize::MAX)) + try_consume!(scan::number(&s[1..], 1, usize::MAX, true)) } else { // if there is no explicit sign, we respect the original `width` - try_consume!(scan::number(s, 1, width)) + try_consume!(scan::number(s, 1, width, true)) } } else { - try_consume!(scan::number(s, 1, width)) + try_consume!(scan::number(s, 1, width, true)) }; set(parsed, v)?; } @@ -765,6 +764,7 @@ mod tests { check(" + 42", &[Space(" "), num(Year)], Err(INVALID)); check("-", &[num(Year)], Err(TOO_SHORT)); check("+", &[num(Year)], Err(TOO_SHORT)); + check("-9223372036854775808", &[num(Timestamp)], parsed!(timestamp: i64::MIN)); // unsigned numeric check("345", &[num(Ordinal)], parsed!(ordinal: 345)); diff --git a/src/format/scan.rs b/src/format/scan.rs index 5a7c061ad0..eae122915f 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -14,7 +14,7 @@ use crate::Weekday; /// More than `max` digits are consumed up to the first `max` digits. /// Any number that does not fit in `i64` is an error. #[inline] -pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { +pub(super) fn number(s: &str, min: usize, max: usize, positive: bool) -> ParseResult<(&str, i64)> { assert!(min <= max); // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on @@ -25,6 +25,8 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) return Err(TOO_SHORT); } + // We construct the value as a negative integer first, and flip the sign if `positive`. + // This allows us to parse `i64::MIN`. let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() @@ -32,16 +34,22 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) if i < min { return Err(INVALID); } else { + if positive { + n = n.checked_neg().ok_or(OUT_OF_RANGE)?; + } return Ok((&s[i..], n)); } } - n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) { + n = match n.checked_mul(10).and_then(|n| n.checked_sub((c - b'0') as i64)) { Some(n) => n, None => return Err(OUT_OF_RANGE), }; } + if positive { + n = n.checked_neg().ok_or(OUT_OF_RANGE)?; + } Ok((&s[core::cmp::min(max, bytes.len())..], n)) } @@ -50,7 +58,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let origlen = s.len(); - let (s, v) = number(s, 1, 9)?; + let (s, v) = number(s, 1, 9, true)?; let consumed = origlen - s.len(); // scale the number accordingly. @@ -68,7 +76,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { /// Returns the number of whole nanoseconds (0--999,999,999). pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. - let (s, v) = number(s, digits, digits)?; + let (s, v) = number(s, digits, digits, true)?; // scale the number accordingly. static SCALE: [i64; 10] =