Skip to content

Commit 4c3d222

Browse files
dhilstyuankunzhang
authored andcommitted
Restore the old API and add the missing combinators
1 parent 07d4b80 commit 4c3d222

File tree

14 files changed

+1355
-157
lines changed

14 files changed

+1355
-157
lines changed

Cargo.lock

Lines changed: 256 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,17 @@ regex = "1.10.4"
1212
chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] }
1313
nom = "8.0.0"
1414
winnow = "0.5.34"
15+
num-traits = "0.2.19"
16+
17+
[dev-dependencies]
18+
anyhow = "1.0.86"
19+
#winnow = { version="0.5.34", features = ["debug"] }
20+
21+
[features]
22+
debug = ["winnow/debug"]
23+
24+
[[bin]]
25+
path = "bin/main.rs"
26+
name = "parse_datetime"
27+
test = false
28+
bench = false

bin/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use parse_datetime::parse_datetime;
2+
3+
fn main() {
4+
let date: String = std::env::args().nth(1).unwrap_or("".to_string());
5+
println!("{}", parse_datetime(&date).unwrap().format("%+"))
6+
}

fuzz/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ chrono = { version="0.4", default-features=false, features=["std", "alloc", "clo
1414
[dependencies.parse_datetime]
1515
path = "../"
1616

17+
[features]
18+
debug = ["parse_datetime/debug"]
19+
1720
[[bin]]
1821
name = "fuzz_parse_datetime"
1922
path = "fuzz_targets/parse_datetime.rs"
Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,134 @@
11
#![no_main]
2+
#![allow(dead_code)]
23

3-
use libfuzzer_sys::fuzz_target;
4+
use std::fmt::{Debug, Display};
5+
use std::io::{self, Write};
46

5-
fuzz_target!(|data: &[u8]| {
6-
let s = std::str::from_utf8(data).unwrap_or("");
7-
let _ = parse_datetime::parse_datetime(s);
7+
use libfuzzer_sys::arbitrary::{self, Arbitrary};
8+
9+
#[macro_use]
10+
extern crate libfuzzer_sys;
11+
12+
#[derive(Debug)]
13+
struct Format(&'static str);
14+
15+
// These are formats to test the compatibility with GNU
16+
const FORMATS: &[&str] = &["%G-%m-%d %H:%M:%S", "%b %d %Y %H:%M:%S"];
17+
18+
impl<'a> Arbitrary<'a> for Format {
19+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
20+
Ok(Format(u.choose(FORMATS)?))
21+
}
22+
}
23+
24+
struct Input {
25+
year: u32,
26+
month: u32,
27+
day: u32,
28+
hour: u32,
29+
minute: u32,
30+
second: u32,
31+
format: Format,
32+
}
33+
34+
impl<'a> Arbitrary<'a> for Input {
35+
fn arbitrary(
36+
u: &mut libfuzzer_sys::arbitrary::Unstructured<'a>,
37+
) -> libfuzzer_sys::arbitrary::Result<Self> {
38+
let year = u.arbitrary::<u32>()?;
39+
let month = u.arbitrary::<u32>()?;
40+
let day = u.arbitrary::<u32>()?;
41+
let hour = u.arbitrary::<u32>()?;
42+
let minute = u.arbitrary::<u32>()?;
43+
let second = u.arbitrary::<u32>()?;
44+
let format = u.arbitrary::<Format>()?;
45+
46+
// GNU max 2147485547
47+
// chrono max 262143
48+
// chrono outputs + before the year if it is >9999
49+
if !(1..=9999).contains(&year)
50+
|| !(1..=12).contains(&month)
51+
|| !(1..=31).contains(&day)
52+
|| !(0..24).contains(&hour)
53+
|| !(0..60).contains(&minute)
54+
|| !(0..60).contains(&second)
55+
{
56+
return Err(crate::arbitrary::Error::IncorrectFormat);
57+
}
58+
59+
Ok(Input {
60+
year,
61+
month,
62+
day,
63+
hour,
64+
minute,
65+
second,
66+
format,
67+
})
68+
}
69+
}
70+
71+
impl Input {
72+
fn format(&self) -> String {
73+
let Input {
74+
year,
75+
month,
76+
day,
77+
hour,
78+
minute,
79+
second,
80+
format,
81+
} = self;
82+
let as_string = format!("{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}");
83+
std::process::Command::new("date")
84+
.arg("-d")
85+
.arg(as_string)
86+
.arg(format!("+{}", &format.0))
87+
.output()
88+
.map(|mut output| {
89+
output.stdout.pop(); // remove trailing \n
90+
String::from_utf8(output.stdout).expect("from_utf8")
91+
})
92+
.expect("gnu date")
93+
}
94+
}
95+
96+
impl Display for Input {
97+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98+
write!(f, "{}", self.format())
99+
}
100+
}
101+
102+
impl Debug for Input {
103+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104+
write!(f, "{}", self.format())
105+
}
106+
}
107+
108+
fuzz_target!(|input: Input| {
109+
let fmt = input.format.0;
110+
let gnu = std::process::Command::new("date")
111+
.arg("-d")
112+
.arg(input.format())
113+
.arg(format!("+{fmt}"))
114+
.output()
115+
.map(|mut output| {
116+
output.stdout.pop(); // remove trailing \n
117+
String::from_utf8(output.stdout).expect("from_utf8")
118+
});
119+
let us = parse_datetime::parse_datetime(&input.format()).map(|d| d.format(fmt).to_string());
120+
121+
match (us, gnu) {
122+
(Ok(us), Ok(gnu)) => assert_eq!(
123+
us, gnu,
124+
"\n\nGNU Incompatibility found for the input: {input}\nExpected: {gnu}\nFound: {us}\n\n"
125+
),
126+
(Err(_), Err(_)) => (),
127+
(Ok(us), Err(e)) => {
128+
panic!("Expecting to fail, but succeeded for input `{input}`, gnu error: {e}, parsed date: {us}")
129+
}
130+
(Err(_), Ok(gnu)) => {
131+
panic!("Expecting to succeed, but failed for input `{input}`, gnu output: {gnu}")
132+
}
133+
};
8134
});

src/items/combined.rs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
//! > seconds are allowed, with either comma or period preceding the fraction.
1414
//! > ISO 8601 fractional minutes and hours are not supported. Typically, hosts
1515
//! > support nanosecond timestamp resolution; excess precision is silently discarded.
16+
#![allow(deprecated)]
1617

17-
use winnow::{combinator::alt, seq, PResult, Parser};
18+
use winnow::ascii::dec_uint;
19+
use winnow::token::take;
20+
use winnow::{combinator::alt, seq, trace::trace, PResult, Parser};
1821

22+
use crate::items::combined;
1923
use crate::items::space;
2024

2125
use super::{
@@ -24,22 +28,56 @@ use super::{
2428
time::{self, Time},
2529
};
2630

27-
#[derive(PartialEq, Debug, Clone)]
31+
#[derive(PartialEq, Debug, Clone, Default)]
2832
pub struct DateTime {
29-
date: Date,
30-
time: Time,
33+
pub(crate) date: Date,
34+
pub(crate) time: Time,
3135
}
3236

3337
pub fn parse(input: &mut &str) -> PResult<DateTime> {
38+
alt((
39+
parse_basic,
40+
//parse_8digits
41+
))
42+
.parse_next(input)
43+
}
44+
45+
fn parse_basic(input: &mut &str) -> PResult<DateTime> {
3446
seq!(DateTime {
35-
date: date::iso,
47+
date: trace("date iso", date::iso),
3648
// Note: the `T` is lowercased by the main parse function
3749
_: alt((s('t').void(), (' ', space).void())),
38-
time: time::iso,
50+
time: trace("time iso", time::iso),
3951
})
4052
.parse_next(input)
4153
}
4254

55+
#[allow(dead_code)]
56+
fn parse_8digits(input: &mut &str) -> PResult<DateTime> {
57+
s((
58+
take(2usize).and_then(dec_uint),
59+
take(2usize).and_then(dec_uint),
60+
take(2usize).and_then(dec_uint),
61+
take(2usize).and_then(dec_uint),
62+
))
63+
.map(
64+
|(hour, minute, day, month): (u32, u32, u32, u32)| combined::DateTime {
65+
date: date::Date {
66+
day,
67+
month,
68+
year: None,
69+
},
70+
time: time::Time {
71+
hour,
72+
minute,
73+
second: 0.0,
74+
offset: None,
75+
},
76+
},
77+
)
78+
.parse_next(input)
79+
}
80+
4381
#[cfg(test)]
4482
mod tests {
4583
use super::{parse, DateTime};

src/items/date.rs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ use winnow::{
3030
ascii::{alpha1, dec_uint},
3131
combinator::{alt, opt, preceded},
3232
seq,
33-
token::take,
33+
trace::trace,
3434
PResult, Parser,
3535
};
3636

3737
use super::s;
3838
use crate::ParseDateTimeError;
3939

40-
#[derive(PartialEq, Eq, Clone, Debug)]
40+
#[derive(PartialEq, Eq, Clone, Debug, Default)]
4141
pub struct Date {
4242
pub day: u32,
4343
pub month: u32,
@@ -96,27 +96,24 @@ fn literal2(input: &mut &str) -> PResult<Date> {
9696
.parse_next(input)
9797
}
9898

99-
fn year(input: &mut &str) -> PResult<u32> {
100-
s(alt((
101-
take(4usize).try_map(|x: &str| x.parse()),
102-
take(3usize).try_map(|x: &str| x.parse()),
103-
take(2usize).try_map(|x: &str| x.parse()).map(
104-
|x: u32| {
105-
if x <= 68 {
106-
x + 2000
107-
} else {
108-
x + 1900
109-
}
110-
},
111-
),
112-
)))
99+
pub fn year(input: &mut &str) -> PResult<u32> {
100+
trace(
101+
"year",
102+
dec_uint.try_map(|x| {
103+
(0..=2147485547)
104+
.contains(&x)
105+
.then_some(x)
106+
.ok_or(ParseDateTimeError::InvalidInput)
107+
}),
108+
)
113109
.parse_next(input)
114110
}
115111

116112
fn month(input: &mut &str) -> PResult<u32> {
117113
s(dec_uint)
118114
.try_map(|x| {
119-
(x >= 1 && x <= 12)
115+
(1..=12)
116+
.contains(&x)
120117
.then_some(x)
121118
.ok_or(ParseDateTimeError::InvalidInput)
122119
})
@@ -126,7 +123,8 @@ fn month(input: &mut &str) -> PResult<u32> {
126123
fn day(input: &mut &str) -> PResult<u32> {
127124
s(dec_uint)
128125
.try_map(|x| {
129-
(x >= 1 && x <= 31)
126+
(1..=31)
127+
.contains(&x)
130128
.then_some(x)
131129
.ok_or(ParseDateTimeError::InvalidInput)
132130
})
@@ -229,4 +227,24 @@ mod tests {
229227
assert_eq!(parse(&mut s).unwrap(), reference);
230228
}
231229
}
230+
231+
#[test]
232+
fn test_year() {
233+
use super::year;
234+
235+
// the minimun input length is 2
236+
assert!(year(&mut "0").is_err());
237+
// 2-characters are converted to 19XX/20XX
238+
assert_eq!(year(&mut "00").unwrap(), 2000u32);
239+
assert_eq!(year(&mut "68").unwrap(), 2068u32);
240+
assert_eq!(year(&mut "69").unwrap(), 1969u32);
241+
assert_eq!(year(&mut "99").unwrap(), 1999u32);
242+
// 3,4-characters are converted verbatim
243+
assert_eq!(year(&mut "468").unwrap(), 468u32);
244+
assert_eq!(year(&mut "469").unwrap(), 469u32);
245+
assert_eq!(year(&mut "1568").unwrap(), 1568u32);
246+
assert_eq!(year(&mut "1569").unwrap(), 1569u32);
247+
// consumes at most 4 characters from the input
248+
assert_eq!(year(&mut "1234567").unwrap(), 1234u32);
249+
}
232250
}

0 commit comments

Comments
 (0)