time_parse/duration/
duration_hand.rs1use 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}