1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! This module is used to parse strings to duration.
//!
//! # Example
//! ```
//! use humanize_rs::duration::parse;
//! use std::time::Duration;
//!
//! assert_eq!(parse("1h 30m 71s"), Ok(Duration::from_secs(60 * 90 + 71)));
//! ```

use std::str::from_utf8;
use std::time::Duration;
use ParseError;

const DIGIT_MIN: u8 = b'0';
const DIGIT_MAX: u8 = b'9';

const NANOS: [u64; 7] = [
    1,                         // ns
    1_000,                     // us
    1_000_000,                 // ms
    1_000_000_000,             // s
    60 * 1_000_000_000,        // min
    3600 * 1_000_000_000,      // h
    24 * 3600 * 1_000_000_000, // d
];

/// parse a duration-type string, (e.g. "1h", "1h 30m")
///
/// # Example
/// ```
/// use humanize_rs::duration::parse;
///
///
/// let d = parse("1h 30m").unwrap();
/// println!("{:?}", d);
/// ```
pub fn parse(s: &str) -> Result<Duration, ParseError> {
    let input = s.trim();
    if input.is_empty() {
        return Err(ParseError::EmptyInput);
    }

    if input == "0" {
        return Ok(Duration::new(0, 0));
    }

    let mut value: u64 = 0;

    let bs = input.as_bytes();
    let mut read: usize = 0;

    while read < bs.len() {
        let (v, consumed) = read_int(&bs[read..])?;
        read += consumed;

        let (unit, consumed) = read_unit(&bs[read..])?;
        read += consumed;

        let nanos = unit_to_nanos(unit)?;

        value = v
            .checked_mul(nanos)
            .and_then(|res| value.checked_add(res))
            .ok_or(ParseError::Overflow)?;
    }

    Ok(Duration::from_nanos(value))
}

fn read_int(bs: &[u8]) -> Result<(u64, usize), ParseError> {
    let mut v: u64 = 0;
    let mut read: usize = 0;
    while read < bs.len() {
        let c = bs[read];
        if c < DIGIT_MIN || c > DIGIT_MAX {
            break;
        }

        v = v
            .checked_mul(10)
            .and_then(|res| res.checked_add((c - DIGIT_MIN) as u64))
            .ok_or(ParseError::Overflow)?;

        read += 1;
    }

    if read == 0 {
        return Err(ParseError::MissingValue);
    }

    Ok((v, read))
}

fn read_unit(bs: &[u8]) -> Result<(&str, usize), ParseError> {
    let mut read: usize = 0;
    while read < bs.len() {
        let c = bs[read];
        if DIGIT_MIN <= c && c <= DIGIT_MAX {
            break;
        }

        read += 1;
    }

    if read == 0 {
        return Err(ParseError::MissingUnit);
    }

    let unit = from_utf8(&bs[..read]).or(Err(ParseError::InvalidUnit))?;

    Ok((unit.trim(), read))
}

fn unit_to_nanos(unit: &str) -> Result<u64, ParseError> {
    match unit {
        "ns" => Ok(NANOS[0]),
        "us" => Ok(NANOS[1]),
        "ms" => Ok(NANOS[2]),
        "s" => Ok(NANOS[3]),
        "m" => Ok(NANOS[4]),
        "h" => Ok(NANOS[5]),
        "d" => Ok(NANOS[6]),
        _ => Err(ParseError::InvalidUnit),
    }
}

#[cfg(test)]
mod tests;