time_parse/duration/
duration_hand.rs

1use std::iter::Peekable;
2use std::str::FromStr;
3use std::time::Duration;
4
5use anyhow::anyhow;
6use anyhow::bail;
7use anyhow::ensure;
8use anyhow::Result;
9
10struct Parts<'s> {
11    inner: &'s str,
12}
13
14impl<'s> Parts<'s> {
15    fn new(inner: &str) -> Parts {
16        Parts { inner }
17    }
18}
19
20impl<'s> Iterator for Parts<'s> {
21    type Item = (&'s str, char);
22
23    fn next(&mut self) -> Option<(&'s str, char)> {
24        self.inner
25            .find(|c: char| c.is_ascii_alphabetic())
26            .map(|next| {
27                let (init, point) = self.inner.split_at(next);
28                self.inner = &point[1..];
29                (init, point.as_bytes()[0].to_ascii_uppercase() as char)
30            })
31    }
32}
33
34fn maybe_take(parts: &mut Peekable<Parts>, token: char, mul: u64) -> Result<u64> {
35    Ok(match parts.peek().cloned() {
36        Some((body, found_token)) if found_token == token => {
37            parts.next().unwrap();
38            u64::from_str(body)? * mul
39        }
40        _ => 0,
41    })
42}
43
44fn take_empty(parts: &mut Peekable<Parts>, token: char) -> Result<()> {
45    match parts.next() {
46        Some(("", avail)) if avail == token => Ok(()),
47        Some((head, avail)) if avail == token => {
48            bail!("invalid data before '{}': {:?}", token, head)
49        }
50        other => bail!("expected '{}', not {:?}", token, other),
51    }
52}
53
54pub fn parse(input: &str) -> Result<Duration> {
55    let mut parts = Parts::new(input).peekable();
56
57    let mut seconds = 0u64;
58    let mut nanos = 0u32;
59
60    take_empty(&mut parts, 'P')?;
61
62    seconds += maybe_take(&mut parts, 'W', super::SECS_PER_WEEK)?;
63    seconds += maybe_take(&mut parts, 'D', super::SECS_PER_DAY)?;
64
65    take_empty(&mut parts, 'T')?;
66
67    seconds += maybe_take(&mut parts, 'H', super::SECS_PER_HOUR)?;
68    seconds += maybe_take(&mut parts, 'M', super::SECS_PER_MINUTE)?;
69
70    if let Some((mut body, 'S')) = parts.peek() {
71        parts.next().unwrap();
72
73        if let Some(first_point) = body.find('.') {
74            let (main, after) = body.split_at(first_point);
75            body = main;
76            nanos = super::to_nanos(&after[1..]).ok_or(anyhow!("invalid nanos"))?;
77        }
78
79        seconds += u64::from_str(body)?;
80    }
81
82    ensure!(
83        parts.peek().is_none(),
84        "unexpected trailing data: {:?}",
85        parts.next().unwrap(),
86    );
87
88    Ok(Duration::new(seconds, nanos))
89}
90
91#[cfg(test)]
92mod tests {
93    use std::time::Duration;
94
95    #[test]
96    fn duration() {
97        use super::parse;
98        assert_eq!(Duration::new(7, 0), parse("PT7S").unwrap());
99        assert_eq!(Duration::new(7, 5_000_000), parse("PT7.005S").unwrap());
100        assert_eq!(Duration::new(2 * 60, 0), parse("PT2M").unwrap());
101        assert_eq!(
102            Duration::new((2 * 24 + 1) * 60 * 60, 0),
103            parse("P2DT1H").unwrap()
104        );
105    }
106
107    #[test]
108    fn parts() {
109        let mut p = super::Parts::new("1D23M");
110        assert_eq!(Some(("1", 'D')), p.next());
111        assert_eq!(Some(("23", 'M')), p.next());
112    }
113}