time_parse/duration/
duration_nom.rs

1use std::time::Duration;
2
3use anyhow::bail;
4use anyhow::Result;
5use nom::bytes::complete::tag;
6use nom::combinator::opt;
7use nom::sequence::terminated;
8use nom::IResult;
9
10fn num(input: &str) -> IResult<&str, u64> {
11    let (input, num) = nom::character::complete::digit1(input)?;
12    let num = num
13        .parse()
14        .map_err(|_| nom::Err::Error((input, nom::error::ErrorKind::TooLarge)))?;
15
16    Ok((input, num))
17}
18
19fn period_num(input: &str) -> IResult<&str, (u64, u32)> {
20    let (input, whole) = num(input)?;
21    let (input, dot) = opt(tag("."))(input)?;
22    if dot.is_none() {
23        return Ok((input, (whole, 0)));
24    }
25
26    let (input, frac) = nom::character::complete::digit1(input)?;
27
28    Ok((input, (whole, super::to_nanos(frac).expect("TODO"))))
29}
30
31fn time(input: &str) -> IResult<&str, (u64, u32)> {
32    let (input, _) = tag("T")(input)?;
33    let (input, h) = opt(terminated(num, tag("H")))(input)?;
34    let (input, m) = opt(terminated(num, tag("M")))(input)?;
35    let (input, s) = opt(terminated(period_num, tag("S")))(input)?;
36
37    Ok((
38        input,
39        ((
40            h.unwrap_or(0) * super::SECS_PER_HOUR
41                + m.unwrap_or(0) * super::SECS_PER_MINUTE
42                + s.map(|(s, _ns)| s).unwrap_or(0),
43            s.map(|(_s, ns)| ns).unwrap_or(0),
44        )),
45    ))
46}
47
48fn period(input: &str) -> IResult<&str, (u64, u32)> {
49    let (input, _) = tag("P")(input)?;
50    let (input, w) = opt(terminated(num, tag("W")))(input)?;
51    let (input, d) = opt(terminated(num, tag("D")))(input)?;
52    let (input, t) = opt(time)(input)?;
53
54    Ok((
55        input,
56        (
57            w.unwrap_or(0) * super::SECS_PER_WEEK
58                + d.unwrap_or(0) * super::SECS_PER_DAY
59                + t.map(|(s, _ns)| s).unwrap_or(0),
60            t.map(|(_s, ns)| ns).unwrap_or(0),
61        ),
62    ))
63}
64
65pub fn parse(input: &str) -> Result<Duration> {
66    match period(input) {
67        Ok(("", (s, ns))) => Ok(Duration::new(s, ns)),
68        other => bail!("invalid: {:?}", other),
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use std::time::Duration;
75
76    #[test]
77    fn duration() {
78        use super::parse;
79        assert_eq!(Duration::new(7, 0), parse("PT7S").unwrap());
80        assert_eq!(Duration::new(7, 5_000_000), parse("PT7.005S").unwrap());
81        assert_eq!(Duration::new(2 * 60, 0), parse("PT2M").unwrap());
82        assert_eq!(
83            Duration::new((2 * 24 + 1) * 60 * 60, 0),
84            parse("P2DT1H").unwrap()
85        );
86        assert!(parse("PT2").is_err());
87        assert!(parse("PT22").is_err());
88        assert!(parse("PT2M2").is_err());
89        assert!(parse("T2S").is_err());
90        assert!(parse("P2S").is_err());
91    }
92}