Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 99bc2d5

Browse files
committedMar 2, 2024·
Support parsing negative timestamps
1 parent 5304354 commit 99bc2d5

File tree

2 files changed

+29
-21
lines changed

2 files changed

+29
-21
lines changed
 

‎src/format/parse.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
109109
}
110110

111111
s = s.trim_start();
112-
parsed.set_day(try_consume!(scan::number(s, 1, 2)))?;
112+
parsed.set_day(try_consume!(scan::number(s, 1, 2, true)))?;
113113
s = scan::space(s)?; // mandatory
114114
parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?;
115115
s = scan::space(s)?; // mandatory
116116

117117
// distinguish two- and three-digit years from four-digit years
118118
let prevlen = s.len();
119-
let mut year = try_consume!(scan::number(s, 2, usize::MAX));
119+
let mut year = try_consume!(scan::number(s, 2, usize::MAX, true));
120120
let yearlen = prevlen - s.len();
121121
match (yearlen, year) {
122122
(2, 0..=49) => {
@@ -133,12 +133,12 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
133133
parsed.set_year(year)?;
134134

135135
s = scan::space(s)?; // mandatory
136-
parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
136+
parsed.set_hour(try_consume!(scan::number(s, 2, 2, true)))?;
137137
s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S
138-
parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
138+
parsed.set_minute(try_consume!(scan::number(s, 2, 2, true)))?;
139139
if let Ok(s_) = scan::char(s.trim_start(), b':') {
140140
// [ ":" *S 2DIGIT ]
141-
parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?;
141+
parsed.set_second(try_consume!(scan::number(s_, 2, 2, true)))?;
142142
}
143143

144144
s = scan::space(s)?; // mandatory
@@ -190,23 +190,23 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes
190190
//
191191
// - For readability a full-date and a full-time may be separated by a space character.
192192

193-
parsed.set_year(try_consume!(scan::number(s, 4, 4)))?;
193+
parsed.set_year(try_consume!(scan::number(s, 4, 4, true)))?;
194194
s = scan::char(s, b'-')?;
195-
parsed.set_month(try_consume!(scan::number(s, 2, 2)))?;
195+
parsed.set_month(try_consume!(scan::number(s, 2, 2, true)))?;
196196
s = scan::char(s, b'-')?;
197-
parsed.set_day(try_consume!(scan::number(s, 2, 2)))?;
197+
parsed.set_day(try_consume!(scan::number(s, 2, 2, true)))?;
198198

199199
s = match s.as_bytes().first() {
200200
Some(&b't' | &b'T' | &b' ') => &s[1..],
201201
Some(_) => return Err(INVALID),
202202
None => return Err(TOO_SHORT),
203203
};
204204

205-
parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
205+
parsed.set_hour(try_consume!(scan::number(s, 2, 2, true)))?;
206206
s = scan::char(s, b':')?;
207-
parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
207+
parsed.set_minute(try_consume!(scan::number(s, 2, 2, true)))?;
208208
s = scan::char(s, b':')?;
209-
parsed.set_second(try_consume!(scan::number(s, 2, 2)))?;
209+
parsed.set_second(try_consume!(scan::number(s, 2, 2, true)))?;
210210
if s.starts_with('.') {
211211
let nanosecond = try_consume!(scan::nanosecond(&s[1..]));
212212
parsed.set_nanosecond(nanosecond)?;
@@ -357,7 +357,7 @@ where
357357
Minute => (2, false, Parsed::set_minute),
358358
Second => (2, false, Parsed::set_second),
359359
Nanosecond => (9, false, Parsed::set_nanosecond),
360-
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
360+
Timestamp => (usize::MAX, true, Parsed::set_timestamp),
361361

362362
// for the future expansion
363363
Internal(ref int) => match int._dummy {},
@@ -366,16 +366,15 @@ where
366366
s = s.trim_start();
367367
let v = if signed {
368368
if s.starts_with('-') {
369-
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
370-
0i64.checked_sub(v).ok_or(OUT_OF_RANGE)?
369+
try_consume!(scan::number(&s[1..], 1, usize::MAX, false))
371370
} else if s.starts_with('+') {
372-
try_consume!(scan::number(&s[1..], 1, usize::MAX))
371+
try_consume!(scan::number(&s[1..], 1, usize::MAX, true))
373372
} else {
374373
// if there is no explicit sign, we respect the original `width`
375-
try_consume!(scan::number(s, 1, width))
374+
try_consume!(scan::number(s, 1, width, true))
376375
}
377376
} else {
378-
try_consume!(scan::number(s, 1, width))
377+
try_consume!(scan::number(s, 1, width, true))
379378
};
380379
set(parsed, v)?;
381380
}
@@ -765,6 +764,7 @@ mod tests {
765764
check(" + 42", &[Space(" "), num(Year)], Err(INVALID));
766765
check("-", &[num(Year)], Err(TOO_SHORT));
767766
check("+", &[num(Year)], Err(TOO_SHORT));
767+
check("-9223372036854775808", &[num(Timestamp)], parsed!(timestamp: i64::MIN));
768768

769769
// unsigned numeric
770770
check("345", &[num(Ordinal)], parsed!(ordinal: 345));

‎src/format/scan.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::Weekday;
1414
/// More than `max` digits are consumed up to the first `max` digits.
1515
/// Any number that does not fit in `i64` is an error.
1616
#[inline]
17-
pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
17+
pub(super) fn number(s: &str, min: usize, max: usize, positive: bool) -> ParseResult<(&str, i64)> {
1818
assert!(min <= max);
1919

2020
// We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
@@ -25,23 +25,31 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)
2525
return Err(TOO_SHORT);
2626
}
2727

28+
// We construct the value as a negative integer first, and flip the sign if `positive`.
29+
// This allows us to parse `i64::MIN`.
2830
let mut n = 0i64;
2931
for (i, c) in bytes.iter().take(max).cloned().enumerate() {
3032
// cloned() = copied()
3133
if !c.is_ascii_digit() {
3234
if i < min {
3335
return Err(INVALID);
3436
} else {
37+
if positive {
38+
n = n.checked_neg().ok_or(OUT_OF_RANGE)?;
39+
}
3540
return Ok((&s[i..], n));
3641
}
3742
}
3843

39-
n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) {
44+
n = match n.checked_mul(10).and_then(|n| n.checked_sub((c - b'0') as i64)) {
4045
Some(n) => n,
4146
None => return Err(OUT_OF_RANGE),
4247
};
4348
}
4449

50+
if positive {
51+
n = n.checked_neg().ok_or(OUT_OF_RANGE)?;
52+
}
4553
Ok((&s[core::cmp::min(max, bytes.len())..], n))
4654
}
4755

@@ -50,7 +58,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)
5058
pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
5159
// record the number of digits consumed for later scaling.
5260
let origlen = s.len();
53-
let (s, v) = number(s, 1, 9)?;
61+
let (s, v) = number(s, 1, 9, true)?;
5462
let consumed = origlen - s.len();
5563

5664
// scale the number accordingly.
@@ -68,7 +76,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
6876
/// Returns the number of whole nanoseconds (0--999,999,999).
6977
pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
7078
// record the number of digits consumed for later scaling.
71-
let (s, v) = number(s, digits, digits)?;
79+
let (s, v) = number(s, digits, digits, true)?;
7280

7381
// scale the number accordingly.
7482
static SCALE: [i64; 10] =

0 commit comments

Comments
 (0)
Please sign in to comment.