go_parse_duration/
lib.rs

1//!
2//! parse-duration-rs is a Rust port of Golang parse duration `time.ParseDuration`.
3//! It parses a duration string in a short form such as `100ms`, `1h45m`, and `3ns`
4//! and return duration in nanoseconds.
5//! 
6//! The crate is called `go-parse-duration` and you can depend on it via cargo:
7//! 
8//! ```ini
9//! [dependencies]
10//! go-parse-duration = "0.1"
11//! ```
12//! 
13//! ## Example
14//! 
15//! ```rust
16//! use go_parse_duration::{parse_duration, Error};
17//! 
18//! fn parse() -> Result<i64, Error> {
19//!   let d = parse_duration("300us")?;
20//!   Ok(d)
21//! }
22//! ```
23//! 
24//! **Usage with Chrono**
25//! 
26//! Converting to Chrono duration can be done easily:
27//! 
28//! ```rust
29//! use chrono::Duration;
30//! use go_parse_duration::{parse_duration, Error};
31//! 
32//! fn parse() -> Result<Duration, Error> {
33//!   let d = parse_duration("1m")?;
34//!   Ok(Duration::nanoseconds(d))
35//! }
36//! ```
37//!
38
39#[derive(Debug, PartialEq)]
40pub enum Error {
41    ParseError(String),
42}
43
44enum InternalError {
45    Overflow,
46}
47
48/// parse_duration parses a duration string and return duration in nanoseconds.
49///
50/// A duration string is a possibly signed sequence of decimal numbers, each
51/// with optional fraction and a unit suffix, such as "300ms", "-1.5h", or 
52/// "2h45m".
53///
54/// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
55pub fn parse_duration(string: &str) -> Result<i64, Error> {
56    // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
57    let mut s = string;
58    let mut d: i64 = 0; // duration to be returned
59    let mut neg = false;
60
61    // Consume [-+]?
62    if s != "" {
63        let c = s.chars().nth(0).unwrap();
64        if c == '-' || c == '+' {
65            neg = c == '-';
66            s = &s[1..];
67        }
68    }
69    // Special case: if all that is left is "0", this is zero.
70    if s == "0" {
71        return Ok(0);
72    }
73    if s == "" {
74        return Err(Error::ParseError(format!("invalid duration: {}", string)));
75    }
76    while s != "" {
77        // integers before, after decimal point
78        let mut v: i64;
79        let mut f: i64 = 0;
80        // value = v + f / scale
81        let mut scale: f64 = 1f64;
82
83        // The next character must be [0-9.]
84        let c = s.chars().nth(0).unwrap();
85        if !(c == '.' || '0' <= c && c <= '9') {
86            return Err(Error::ParseError(format!("invalid duration: {}", string)));
87        }
88        // Consume [0-9]*
89        let pl = s.len();
90        match leading_int(s) {
91            Ok((_v, _s)) => {
92                v = _v;
93                s = _s;
94            }
95            Err(_) => {
96                return Err(Error::ParseError(format!("invalid duration: {}", string)));
97            }
98        }
99        let pre = pl != s.len(); // whether we consume anything before a period
100
101		// Consume (\.[0-9]*)?
102        let mut post = false;
103        if s != "" && s.chars().nth(0).unwrap() == '.' {
104            s = &s[1..];
105            let pl = s.len();
106            match leading_fraction(s) {
107                (f_, scale_, s_) => {
108                    f = f_;
109                    scale = scale_;
110                    s = s_;
111                }
112            }
113            post = pl != s.len();
114        }
115        if !pre && !post {
116            // no digits (e.g. ".s" or "-.s")
117            return Err(Error::ParseError(format!("invalid duration: {}", string)));
118        }
119
120        // Consume unit.
121        let mut i = 0;
122        while i < s.len() {
123            let c = s.chars().nth(i).unwrap();
124            if c == '.' || '0' <= c && c <= '9' {
125                break;
126            }
127            i += 1;
128        }
129        if i == 0 {
130            return Err(Error::ParseError(format!("missing unit in duration: {}", string)));
131        }
132        let u = &s[..i];
133        s = &s[i..];
134        let unit = match u {
135            "ns" => 1i64,
136            "us" => 1000i64,
137            "µs" => 1000i64, // U+00B5 = micro symbol
138            "μs" => 1000i64, // U+03BC = Greek letter mu
139            "ms" => 1000000i64,
140            "s" =>  1000000000i64,
141            "m" =>  60000000000i64,
142            "h" =>  3600000000000i64,
143            _ => { 
144                return Err(Error::ParseError(format!("unknown unit {} in duration {}", u, string)));
145            }
146        };
147        if v > (1 << 63 - 1) / unit {
148            // overflow
149            return Err(Error::ParseError(format!("invalid duration {}", string)));
150        }
151        v *= unit;
152        if f > 0 {
153            // f64 is needed to be nanosecond accurate for fractions of hours.
154            // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
155            v += (f as f64 * (unit as f64 / scale)) as i64;
156            if v < 0 {
157                // overflow
158                return Err(Error::ParseError(format!("invalid duration {}", string)));
159            }
160        }
161        d += v;
162        if d < 0 {
163            // overflow
164            return Err(Error::ParseError(format!("invalid duration {}", string)));
165        }
166    }
167    if neg {
168        d = -d;
169    }
170    Ok(d)
171}
172
173// leading_int consumes the leading [0-9]* from s.
174fn leading_int(s: &str) -> Result<(i64, &str), InternalError> {
175    let mut x = 0;
176    let mut i = 0;
177    while i < s.len() {
178        let c = s.chars().nth(i).unwrap();
179        if c < '0' || c > '9' {
180            break
181        }
182        if x > (1<<63-1)/10 {
183            return Err(InternalError::Overflow);
184        }
185        let d = i64::from(c.to_digit(10).unwrap());
186        x = x * 10 + d;
187        if x < 0 {
188            // overflow
189            return Err(InternalError::Overflow);
190        }
191        i += 1;
192    }
193    Ok((x, &s[i..]))
194}
195
196// leading_fraction consumes the leading [0-9]* from s.
197//
198// It is used only for fractions, so does not return an error on overflow,
199// it just stops accumulating precision.
200//
201// It returns (value, scale, remainder) tuple.
202fn leading_fraction(s: &str) -> (i64, f64, &str) {
203    let mut i = 0;
204    let mut x = 0i64;
205    let mut scale = 1f64;
206    let mut overflow = false;
207    while i < s.len() {
208        let c = s.chars().nth(i).unwrap();
209        if c < '0' || c > '9' {
210            break;
211        }
212        if overflow {
213            continue;
214        }
215        if x > (1 << 63 - 1) / 10 {
216            // It's possible for overflow to give a positive number, so take care.
217            overflow = true;
218            continue;
219        }
220        let d = i64::from(c.to_digit(10).unwrap());
221        let y = x * 10 + d;
222        if y < 0 {
223            overflow = true;
224            continue;
225        }
226        x = y;
227        scale *= 10f64;
228        i += 1;
229    }
230    (x, scale, &s[i..])
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_parse_duration() -> Result<(), Error> {
239        assert_eq!(parse_duration("50ns")?, 50);
240        assert_eq!(parse_duration("3ms")?, 3000000);
241        assert_eq!(parse_duration("2us")?, 2000);
242        assert_eq!(parse_duration("4s")?, 4000000000);
243        assert_eq!(parse_duration("1h45m")?, 6300000000000);
244        assert_eq!(
245            parse_duration("1").unwrap_err(), 
246            Error::ParseError(String::from("missing unit in duration: 1")),
247        );
248        Ok(())
249    }
250}
251