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