humanize_rs/duration/
mod.rs

1//! This module is used to parse strings to duration.
2//!
3//! # Example
4//! ```
5//! use humanize_rs::duration::parse;
6//! use std::time::Duration;
7//!
8//! assert_eq!(parse("1h 30m 71s"), Ok(Duration::from_secs(60 * 90 + 71)));
9//! ```
10
11use std::str::from_utf8;
12use std::time::Duration;
13use ParseError;
14
15const DIGIT_MIN: u8 = b'0';
16const DIGIT_MAX: u8 = b'9';
17
18const NANOS: [u64; 7] = [
19    1,                         // ns
20    1_000,                     // us
21    1_000_000,                 // ms
22    1_000_000_000,             // s
23    60 * 1_000_000_000,        // min
24    3600 * 1_000_000_000,      // h
25    24 * 3600 * 1_000_000_000, // d
26];
27
28/// parse a duration-type string, (e.g. "1h", "1h 30m")
29///
30/// # Example
31/// ```
32/// use humanize_rs::duration::parse;
33///
34///
35/// let d = parse("1h 30m").unwrap();
36/// println!("{:?}", d);
37/// ```
38pub fn parse(s: &str) -> Result<Duration, ParseError> {
39    let input = s.trim();
40    if input.is_empty() {
41        return Err(ParseError::EmptyInput);
42    }
43
44    if input == "0" {
45        return Ok(Duration::new(0, 0));
46    }
47
48    let mut value: u64 = 0;
49
50    let bs = input.as_bytes();
51    let mut read: usize = 0;
52
53    while read < bs.len() {
54        let (v, consumed) = read_int(&bs[read..])?;
55        read += consumed;
56
57        let (unit, consumed) = read_unit(&bs[read..])?;
58        read += consumed;
59
60        let nanos = unit_to_nanos(unit)?;
61
62        value = v
63            .checked_mul(nanos)
64            .and_then(|res| value.checked_add(res))
65            .ok_or(ParseError::Overflow)?;
66    }
67
68    Ok(Duration::from_nanos(value))
69}
70
71fn read_int(bs: &[u8]) -> Result<(u64, usize), ParseError> {
72    let mut v: u64 = 0;
73    let mut read: usize = 0;
74    while read < bs.len() {
75        let c = bs[read];
76        if c < DIGIT_MIN || c > DIGIT_MAX {
77            break;
78        }
79
80        v = v
81            .checked_mul(10)
82            .and_then(|res| res.checked_add((c - DIGIT_MIN) as u64))
83            .ok_or(ParseError::Overflow)?;
84
85        read += 1;
86    }
87
88    if read == 0 {
89        return Err(ParseError::MissingValue);
90    }
91
92    Ok((v, read))
93}
94
95fn read_unit(bs: &[u8]) -> Result<(&str, usize), ParseError> {
96    let mut read: usize = 0;
97    while read < bs.len() {
98        let c = bs[read];
99        if DIGIT_MIN <= c && c <= DIGIT_MAX {
100            break;
101        }
102
103        read += 1;
104    }
105
106    if read == 0 {
107        return Err(ParseError::MissingUnit);
108    }
109
110    let unit = from_utf8(&bs[..read]).or(Err(ParseError::InvalidUnit))?;
111
112    Ok((unit.trim(), read))
113}
114
115fn unit_to_nanos(unit: &str) -> Result<u64, ParseError> {
116    match unit {
117        "ns" => Ok(NANOS[0]),
118        "us" => Ok(NANOS[1]),
119        "ms" => Ok(NANOS[2]),
120        "s" => Ok(NANOS[3]),
121        "m" => Ok(NANOS[4]),
122        "h" => Ok(NANOS[5]),
123        "d" => Ok(NANOS[6]),
124        _ => Err(ParseError::InvalidUnit),
125    }
126}
127
128#[cfg(test)]
129mod tests;