humantime/
duration.rs

1use std::fmt;
2use std::str::Chars;
3use std::time::Duration;
4use std::error::Error as StdError;
5
6quick_error! {
7    /// Error parsing human-friendly duration
8    #[derive(Debug, PartialEq, Clone, Copy)]
9    pub enum Error {
10        /// Invalid character during parsing
11        ///
12        /// More specifically anything that is not alphanumeric is prohibited
13        ///
14        /// The field is an byte offset of the character in the string.
15        InvalidCharacter(offset: usize) {
16            display("invalid character at {}", offset)
17            description("invalid character")
18        }
19        /// Non-numeric value where number is expected
20        ///
21        /// This usually means that either time unit is broken into words,
22        /// e.g. `m sec` instead of `msec`, or just number is omitted,
23        /// for example `2 hours min` instead of `2 hours 1 min`
24        ///
25        /// The field is an byte offset of the errorneous character
26        /// in the string.
27        NumberExpected(offset: usize) {
28            display("expected number at {}", offset)
29            description("expected number")
30        }
31        /// Unit in the number is not one of allowed units
32        ///
33        /// See documentation of `parse_duration` for the list of supported
34        /// time units.
35        ///
36        /// The two fields are start and end (exclusive) of the slice from
37        /// the original string, containing errorneous value
38        UnknownUnit(start: usize, end: usize) {
39            display("unknown unit at {}-{}", start, end)
40            description("unknown unit")
41        }
42        /// The numeric value is too large
43        ///
44        /// Usually this means value is too large to be useful. If user writes
45        /// data in subsecond units, then the maximum is about 3k years. When
46        /// using seconds, or larger units, the limit is even larger.
47        NumberOverflow {
48            display(self_) -> ("{}", self_.description())
49            description("number is too large")
50        }
51        /// The value was an empty string (or consists only whitespace)
52        Empty {
53            display(self_) -> ("{}", self_.description())
54            description("value was empty")
55        }
56    }
57
58}
59
60/// A wrapper type that allows you to Display a Duration
61#[derive(Debug, Clone)]
62pub struct FormattedDuration(Duration);
63
64trait OverflowOp: Sized {
65    fn mul(self, other: Self) -> Result<Self, Error>;
66    fn add(self, other: Self) -> Result<Self, Error>;
67}
68
69impl OverflowOp for u64 {
70    fn mul(self, other: Self) -> Result<Self, Error> {
71        self.checked_mul(other).ok_or(Error::NumberOverflow)
72    }
73    fn add(self, other: Self) -> Result<Self, Error> {
74        self.checked_add(other).ok_or(Error::NumberOverflow)
75    }
76}
77
78struct Parser<'a> {
79    iter: Chars<'a>,
80    src: &'a str,
81    current: (u64, u64),
82}
83
84impl<'a> Parser<'a> {
85    fn off(&self) -> usize {
86        self.src.len() - self.iter.as_str().len()
87    }
88
89    fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
90        let off = self.off();
91        for c in self.iter.by_ref() {
92            match c {
93                '0'...'9' => {
94                    return Ok(Some(c as u64 - '0' as u64));
95                }
96                c if c.is_whitespace() => continue,
97                _ => {
98                    return Err(Error::NumberExpected(off));
99                }
100            }
101        }
102        return Ok(None);
103    }
104    fn parse_unit(&mut self, n: u64, start: usize, end: usize)
105        -> Result<(), Error>
106    {
107        let (mut sec, nsec) = match &self.src[start..end] {
108            "nanos" | "nsec" | "ns" => (0u64, n),
109            "usec" | "us" => (0u64, try!(n.mul(1000))),
110            "millis" | "msec" | "ms" => (0u64, try!(n.mul(1000_000))),
111            "seconds" | "second" | "secs" | "sec" | "s" => (n, 0),
112            "minutes" | "minute" | "min" | "mins" | "m"
113            => (try!(n.mul(60)), 0),
114            "hours" | "hour" | "hr" | "hrs" | "h" => (try!(n.mul(3600)), 0),
115            "days" | "day" | "d" => (try!(n.mul(86400)), 0),
116            "weeks" | "week" | "w" => (try!(n.mul(86400*7)), 0),
117            "months" | "month" | "M" => (try!(n.mul(2630016)), 0), // 30.44d
118            "years" | "year" | "y" => (try!(n.mul(31557600)), 0), // 365.25d
119            _ => return Err(Error::UnknownUnit(start, end)),
120        };
121        let mut nsec = try!(self.current.1.add(nsec));
122        if nsec > 1000_000_000 {
123            sec = try!(sec.add(nsec / 1000_000_000));
124            nsec %= 1000_000_000;
125        }
126        sec = try!(self.current.0.add(sec));
127        self.current = (sec, nsec);
128        Ok(())
129    }
130
131    fn parse(mut self) -> Result<Duration, Error> {
132        let mut n = try!(try!(self.parse_first_char()).ok_or(Error::Empty));
133        'outer: loop {
134            let mut off = self.off();
135            while let Some(c) = self.iter.next() {
136                match c {
137                    '0'...'9' => {
138                        n = try!(n.checked_mul(10)
139                            .and_then(|x| x.checked_add(c as u64 - '0' as u64))
140                            .ok_or(Error::NumberOverflow));
141                    }
142                    c if c.is_whitespace() => {}
143                    'a'...'z' | 'A'...'Z' => {
144                        break;
145                    }
146                    _ => {
147                        return Err(Error::InvalidCharacter(off));
148                    }
149                }
150                off = self.off();
151            }
152            let start = off;
153            let mut off = self.off();
154            while let Some(c) = self.iter.next() {
155                match c {
156                    '0'...'9' => {
157                        try!(self.parse_unit(n, start, off));
158                        n = c as u64 - '0' as u64;
159                        continue 'outer;
160                    }
161                    c if c.is_whitespace() => break,
162                    'a'...'z' | 'A'...'Z' => {}
163                    _ => {
164                        return Err(Error::InvalidCharacter(off));
165                    }
166                }
167                off = self.off();
168            }
169            try!(self.parse_unit(n, start, off));
170            n = match try!(self.parse_first_char()) {
171                Some(n) => n,
172                None => return Ok(
173                    Duration::new(self.current.0, self.current.1 as u32)),
174            };
175        }
176    }
177
178}
179
180/// Parse duration object `1hour 12min 5s`
181///
182/// The duration object is a concatenation of time spans. Where each time
183/// span is an integer number and a suffix. Supported suffixes:
184///
185/// * `nsec`, `ns` -- microseconds
186/// * `usec`, `us` -- microseconds
187/// * `msec`, `ms` -- milliseconds
188/// * `seconds`, `second`, `sec`, `s`
189/// * `minutes`, `minute`, `min`, `m`
190/// * `hours`, `hour`, `hr`, `h`
191/// * `days`, `day`, `d`
192/// * `weeks`, `week`, `w`
193/// * `months`, `month`, `M` -- defined as 30.44 days
194/// * `years`, `year`, `y` -- defined as 365.25 days
195///
196/// # Examples
197///
198/// ```
199/// use std::time::Duration;
200/// use humantime::parse_duration;
201///
202/// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0)));
203/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
204/// ```
205pub fn parse_duration(s: &str) -> Result<Duration, Error> {
206    Parser {
207        iter: s.chars(),
208        src: s,
209        current: (0, 0),
210    }.parse()
211}
212
213/// Formats duration into a human-readable string
214///
215/// Note: this format is guaranteed to have same value when using
216/// parse_duration, but we can change some details of the exact composition
217/// of the value.
218///
219/// # Examples
220///
221/// ```
222/// use std::time::Duration;
223/// use humantime::format_duration;
224///
225/// let val1 = Duration::new(9420, 0);
226/// assert_eq!(format_duration(val1).to_string(), "2h 37m");
227/// let val2 = Duration::new(0, 32_000_000);
228/// assert_eq!(format_duration(val2).to_string(), "32ms");
229/// ```
230pub fn format_duration(val: Duration) -> FormattedDuration {
231    FormattedDuration(val)
232}
233
234fn item_plural(f: &mut fmt::Formatter, started: &mut bool,
235    name: &str, value: u64)
236    -> fmt::Result
237{
238    if value > 0 {
239        if *started {
240            f.write_str(" ")?;
241        }
242        write!(f, "{}{}", value, name)?;
243        if value > 1 {
244            f.write_str("s")?;
245        }
246        *started = true;
247    }
248    Ok(())
249}
250fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32)
251    -> fmt::Result
252{
253    if value > 0 {
254        if *started {
255            f.write_str(" ")?;
256        }
257        write!(f, "{}{}", value, name)?;
258        *started = true;
259    }
260    Ok(())
261}
262
263impl fmt::Display for FormattedDuration {
264    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265        let secs = self.0.as_secs();
266        let nanos = self.0.subsec_nanos();
267
268        if secs == 0 && nanos == 0 {
269            f.write_str("0s")?;
270            return Ok(());
271        }
272
273        let years = secs / 31557600;  // 365.25d
274        let ydays = secs % 31557600;
275        let months = ydays / 2630016;  // 30.44d
276        let mdays = ydays % 2630016;
277        let days = mdays / 86400;
278        let day_secs = mdays % 86400;
279        let hours = day_secs / 3600;
280        let minutes = day_secs % 3600 / 60;
281        let seconds = day_secs % 60;
282
283        let millis = nanos / 1_000_000;
284        let micros = nanos / 1000 % 1000;
285        let nanosec = nanos % 1000;
286
287        let ref mut started = false;
288        item_plural(f, started, "year", years)?;
289        item_plural(f, started, "month", months)?;
290        item_plural(f, started, "day", days)?;
291        item(f, started, "h", hours as u32)?;
292        item(f, started, "m", minutes as u32)?;
293        item(f, started, "s", seconds as u32)?;
294        item(f, started, "ms", millis)?;
295        item(f, started, "us", micros)?;
296        item(f, started, "ns", nanosec)?;
297        Ok(())
298    }
299}
300
301#[cfg(test)]
302mod test {
303    extern crate rand;
304
305    use std::time::Duration;
306    use self::rand::Rng;
307    use super::{parse_duration, format_duration};
308    use super::Error;
309
310    #[test]
311    fn test_units() {
312        assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17)));
313        assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17)));
314        assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33)));
315        assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000)));
316        assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000)));
317        assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31000000)));
318        assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31000000)));
319        assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6000000)));
320        assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0)));
321        assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0)));
322        assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0)));
323        assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0)));
324        assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0)));
325        assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0)));
326        assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0)));
327        assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0)));
328        assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0)));
329        assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0)));
330        assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0)));
331        assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0)));
332        assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0)));
333        assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0)));
334        assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0)));
335        assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0)));
336        assert_eq!(parse_duration("2days"), Ok(Duration::new(172800, 0)));
337        assert_eq!(parse_duration("365d"), Ok(Duration::new(31536000, 0)));
338        assert_eq!(parse_duration("1week"), Ok(Duration::new(604800, 0)));
339        assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4233600, 0)));
340        assert_eq!(parse_duration("52w"), Ok(Duration::new(31449600, 0)));
341        assert_eq!(parse_duration("1month"), Ok(Duration::new(2630016, 0)));
342        assert_eq!(parse_duration("3months"), Ok(Duration::new(3*2630016, 0)));
343        assert_eq!(parse_duration("12M"), Ok(Duration::new(31560192, 0)));
344        assert_eq!(parse_duration("1year"), Ok(Duration::new(31557600, 0)));
345        assert_eq!(parse_duration("7years"), Ok(Duration::new(7*31557600, 0)));
346        assert_eq!(parse_duration("17y"), Ok(Duration::new(536479200, 0)));
347    }
348
349    #[test]
350    fn test_combo() {
351        assert_eq!(parse_duration("20 min 17 nsec "), Ok(Duration::new(1200, 17)));
352        assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0)));
353    }
354
355    #[test]
356    fn all_86400_seconds() {
357        for second in 0..86400 {  // scan leap year and non-leap year
358            let d = Duration::new(second, 0);
359            assert_eq!(d,
360                parse_duration(&format_duration(d).to_string()).unwrap());
361        }
362    }
363
364    #[test]
365    fn random_second() {
366        for _ in 0..10000 {
367            let sec = rand::thread_rng().gen_range(0, 253370764800);
368            let d = Duration::new(sec, 0);
369            assert_eq!(d,
370                parse_duration(&format_duration(d).to_string()).unwrap());
371        }
372    }
373
374    #[test]
375    fn random_any() {
376        for _ in 0..10000 {
377            let sec = rand::thread_rng().gen_range(0, 253370764800);
378            let nanos = rand::thread_rng().gen_range(0, 1_000_000_000);
379            let d = Duration::new(sec, nanos);
380            assert_eq!(d,
381                parse_duration(&format_duration(d).to_string()).unwrap());
382        }
383    }
384
385    #[test]
386    fn test_overlow() {
387        // Overflow on subseconds is earlier because of how we do conversion
388        // we could fix it, but I don't see any good reason for this
389        assert_eq!(parse_duration("100000000000000000000ns"),
390            Err(Error::NumberOverflow));
391        assert_eq!(parse_duration("100000000000000000us"),
392            Err(Error::NumberOverflow));
393        assert_eq!(parse_duration("100000000000000ms"),
394            Err(Error::NumberOverflow));
395
396        assert_eq!(parse_duration("100000000000000000000s"),
397            Err(Error::NumberOverflow));
398        assert_eq!(parse_duration("10000000000000000000m"),
399            Err(Error::NumberOverflow));
400        assert_eq!(parse_duration("1000000000000000000h"),
401            Err(Error::NumberOverflow));
402        assert_eq!(parse_duration("100000000000000000d"),
403            Err(Error::NumberOverflow));
404        assert_eq!(parse_duration("10000000000000000w"),
405            Err(Error::NumberOverflow));
406        assert_eq!(parse_duration("1000000000000000M"),
407            Err(Error::NumberOverflow));
408        assert_eq!(parse_duration("10000000000000y"),
409            Err(Error::NumberOverflow));
410    }
411}