humantime/
duration.rs

1use std::error::Error as StdError;
2use std::fmt;
3use std::str::{Chars, FromStr};
4use std::time::Duration;
5
6/// Error parsing human-friendly duration
7#[derive(Debug, PartialEq, Clone)]
8pub 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 exceeds the limits of this library.
42    ///
43    /// This can mean two things:
44    /// - The value is too large to be useful.
45    ///   For instance, the maximum duration written with subseconds unit is about 3000 years.
46    /// - The attempted precision is not supported.
47    ///   For instance, a duration of `0.5ns` is not supported,
48    ///   because durations below one nanosecond cannot be represented.
49    // NOTE: it would be more logical to create a separate `NumberPrecisionLimit` error,
50    // but that would be a breaking change. Reconsider this for the next major version.
51    NumberOverflow,
52    /// The value was an empty string (or consists only whitespace)
53    Empty,
54}
55
56impl StdError for Error {}
57
58impl fmt::Display for Error {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset),
62            Error::NumberExpected(offset) => write!(f, "expected number at {}", offset),
63            Error::UnknownUnit { unit, value, .. } if unit.is_empty() => {
64                write!(f, "time unit needed, for example {0}sec or {0}ms", value)
65            }
66            Error::UnknownUnit { unit, .. } => {
67                write!(
68                    f,
69                    "unknown time unit {:?}, \
70                    supported units: ns, us/µs, ms, sec, min, hours, days, \
71                    weeks, months, years (and few variations)",
72                    unit
73                )
74            }
75            Error::NumberOverflow => write!(f, "number is too large or cannot be represented without a lack of precision (values below 1ns are not supported)"),
76            Error::Empty => write!(f, "value was empty"),
77        }
78    }
79}
80
81/// A wrapper type that allows you to Display a Duration
82#[derive(Debug, Clone)]
83pub struct FormattedDuration(Duration);
84
85trait OverflowOp: Sized {
86    fn mul(self, other: Self) -> Result<Self, Error>;
87    fn add(self, other: Self) -> Result<Self, Error>;
88    fn div(self, other: Self) -> Result<Self, Error>;
89}
90
91impl OverflowOp for u64 {
92    fn mul(self, other: Self) -> Result<Self, Error> {
93        self.checked_mul(other).ok_or(Error::NumberOverflow)
94    }
95    fn add(self, other: Self) -> Result<Self, Error> {
96        self.checked_add(other).ok_or(Error::NumberOverflow)
97    }
98    fn div(self, other: Self) -> Result<Self, Error> {
99        match self % other {
100            0 => Ok(self / other),
101            _ => Err(Error::NumberOverflow),
102        }
103    }
104}
105
106#[derive(Debug, Clone, Copy)]
107struct Fraction {
108    numerator: u64,
109    denominator: u64,
110}
111
112struct Parser<'a> {
113    iter: Chars<'a>,
114    src: &'a str,
115}
116
117impl Parser<'_> {
118    fn parse(mut self) -> Result<Duration, Error> {
119        let mut n = self.parse_first_char()?.ok_or(Error::Empty)?; // integer part
120        let mut out = Duration::ZERO;
121        'outer: loop {
122            let mut frac = None; // fractional part
123            let mut off = self.off();
124            while let Some(c) = self.iter.next() {
125                match c {
126                    '0'..='9' => {
127                        n = n
128                            .checked_mul(10)
129                            .and_then(|x| x.checked_add(c as u64 - '0' as u64))
130                            .ok_or(Error::NumberOverflow)?;
131                    }
132                    c if c.is_whitespace() => {}
133                    'a'..='z' | 'A'..='Z' | 'µ' => {
134                        break;
135                    }
136                    '.' => {
137                        // decimal separator, the fractional part begins now
138                        frac = Some(self.parse_fractional_part(&mut off)?);
139                        break;
140                    }
141                    _ => {
142                        return Err(Error::InvalidCharacter(off));
143                    }
144                }
145                off = self.off();
146            }
147            let start = off;
148            let mut off = self.off();
149            while let Some(c) = self.iter.next() {
150                match c {
151                    '0'..='9' => {
152                        self.parse_unit(n, frac, start, off, &mut out)?;
153                        n = c as u64 - '0' as u64;
154                        continue 'outer;
155                    }
156                    c if c.is_whitespace() => break,
157                    'a'..='z' | 'A'..='Z' | 'µ' => {}
158                    _ => {
159                        return Err(Error::InvalidCharacter(off));
160                    }
161                }
162                off = self.off();
163            }
164
165            self.parse_unit(n, frac, start, off, &mut out)?;
166            n = match self.parse_first_char()? {
167                Some(n) => n,
168                None => return Ok(out),
169            };
170        }
171    }
172
173    fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
174        let off = self.off();
175        for c in self.iter.by_ref() {
176            match c {
177                '0'..='9' => {
178                    return Ok(Some(c as u64 - '0' as u64));
179                }
180                c if c.is_whitespace() => continue,
181                _ => {
182                    return Err(Error::NumberExpected(off));
183                }
184            }
185        }
186        Ok(None)
187    }
188
189    fn parse_fractional_part(&mut self, off: &mut usize) -> Result<Fraction, Error> {
190        let mut numerator = 0u64;
191        let mut denominator = 1u64;
192        let mut zeros = true;
193        while let Some(c) = self.iter.next() {
194            match c {
195                '0' => {
196                    denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?;
197                    if !zeros {
198                        numerator = numerator.checked_mul(10).ok_or(Error::NumberOverflow)?;
199                    }
200                }
201                '1'..='9' => {
202                    zeros = false;
203                    denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?;
204                    numerator = numerator
205                        .checked_mul(10)
206                        .and_then(|x| x.checked_add(c as u64 - '0' as u64))
207                        .ok_or(Error::NumberOverflow)?;
208                }
209                c if c.is_whitespace() => {}
210                'a'..='z' | 'A'..='Z' | 'µ' => {
211                    break;
212                }
213                _ => {
214                    return Err(Error::InvalidCharacter(*off));
215                }
216            };
217            // update the offset used by the parsing loop
218            *off = self.off();
219        }
220        if denominator == 1 {
221            // no digits were given after the separator, e.g. "1."
222            return Err(Error::InvalidCharacter(*off));
223        }
224        Ok(Fraction {
225            numerator,
226            denominator,
227        })
228    }
229
230    fn off(&self) -> usize {
231        self.src.len() - self.iter.as_str().len()
232    }
233
234    fn parse_unit(
235        &mut self,
236        n: u64,
237        frac: Option<Fraction>,
238        start: usize,
239        end: usize,
240        out: &mut Duration,
241    ) -> Result<(), Error> {
242        let unit = match Unit::from_str(&self.src[start..end]) {
243            Ok(u) => u,
244            Err(()) => {
245                return Err(Error::UnknownUnit {
246                    start,
247                    end,
248                    unit: self.src[start..end].to_owned(),
249                    value: n,
250                });
251            }
252        };
253
254        // add the integer part
255        let (sec, nsec) = match unit {
256            Unit::Nanosecond => (0u64, n),
257            Unit::Microsecond => (0u64, n.mul(1000)?),
258            Unit::Millisecond => (0u64, n.mul(1_000_000)?),
259            Unit::Second => (n, 0),
260            Unit::Minute => (n.mul(60)?, 0),
261            Unit::Hour => (n.mul(3600)?, 0),
262            Unit::Day => (n.mul(86400)?, 0),
263            Unit::Week => (n.mul(86400 * 7)?, 0),
264            Unit::Month => (n.mul(2_630_016)?, 0), // 30.44d
265            Unit::Year => (n.mul(31_557_600)?, 0), // 365.25d
266        };
267        add_current(sec, nsec, out)?;
268
269        // add the fractional part
270        if let Some(Fraction {
271            numerator: n,
272            denominator: d,
273        }) = frac
274        {
275            let (sec, nsec) = match unit {
276                Unit::Nanosecond => return Err(Error::NumberOverflow),
277                Unit::Microsecond => (0, n.mul(1000)?.div(d)?),
278                Unit::Millisecond => (0, n.mul(1_000_000)?.div(d)?),
279                Unit::Second => (0, n.mul(1_000_000_000)?.div(d)?),
280                Unit::Minute => (0, n.mul(60_000_000_000)?.div(d)?),
281                Unit::Hour => (n.mul(3600)?.div(d)?, 0),
282                Unit::Day => (n.mul(86400)?.div(d)?, 0),
283                Unit::Week => (n.mul(86400 * 7)?.div(d)?, 0),
284                Unit::Month => (n.mul(2_630_016)?.div(d)?, 0), // 30.44d
285                Unit::Year => (n.mul(31_557_600)?.div(d)?, 0), // 365.25d
286            };
287            add_current(sec, nsec, out)?;
288        }
289
290        Ok(())
291    }
292}
293
294fn add_current(mut sec: u64, nsec: u64, out: &mut Duration) -> Result<(), Error> {
295    let mut nsec = (out.subsec_nanos() as u64).add(nsec)?;
296    if nsec > 1_000_000_000 {
297        sec = sec.add(nsec / 1_000_000_000)?;
298        nsec %= 1_000_000_000;
299    }
300    sec = out.as_secs().add(sec)?;
301    *out = Duration::new(sec, nsec as u32);
302    Ok(())
303}
304
305enum Unit {
306    Nanosecond,
307    Microsecond,
308    Millisecond,
309    Second,
310    Minute,
311    Hour,
312    Day,
313    Week,
314    Month,
315    Year,
316}
317
318impl FromStr for Unit {
319    type Err = ();
320
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        match s {
323            "nanos" | "nsec" | "ns" => Ok(Self::Nanosecond),
324            "usec" | "us" | "µs" => Ok(Self::Microsecond),
325            "millis" | "msec" | "ms" => Ok(Self::Millisecond),
326            "seconds" | "second" | "secs" | "sec" | "s" => Ok(Self::Second),
327            "minutes" | "minute" | "min" | "mins" | "m" => Ok(Self::Minute),
328            "hours" | "hour" | "hr" | "hrs" | "h" => Ok(Self::Hour),
329            "days" | "day" | "d" => Ok(Self::Day),
330            "weeks" | "week" | "wk" | "wks" | "w" => Ok(Self::Week),
331            "months" | "month" | "M" => Ok(Self::Month),
332            "years" | "year" | "yr" | "yrs" | "y" => Ok(Self::Year),
333            _ => Err(()),
334        }
335    }
336}
337
338/// Parse duration object `1hour 12min 5s`
339///
340/// The duration object is a concatenation of time spans. Where each time
341/// span is an integer number and a suffix. Supported suffixes:
342///
343/// * `nsec`, `ns` -- nanoseconds
344/// * `usec`, `us`, `µs` -- microseconds
345/// * `msec`, `ms` -- milliseconds
346/// * `seconds`, `second`, `sec`, `s`
347/// * `minutes`, `minute`, `min`, `m`
348/// * `hours`, `hour`, `hr`, `hrs`, `h`
349/// * `days`, `day`, `d`
350/// * `weeks`, `week`, `wk`, `wks`, `w`
351/// * `months`, `month`, `M` -- defined as 30.44 days
352/// * `years`, `year`, `yr`, `yrs`, `y` -- defined as 365.25 days
353///
354/// # Examples
355///
356/// ```
357/// use std::time::Duration;
358/// use humantime::parse_duration;
359///
360/// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0)));
361/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
362/// assert_eq!(parse_duration("4.2s"), Ok(Duration::new(4, 200_000_000)));
363/// ```
364pub fn parse_duration(s: &str) -> Result<Duration, Error> {
365    if s == "0" {
366        return Ok(Duration::ZERO);
367    }
368    Parser {
369        iter: s.chars(),
370        src: s,
371    }
372    .parse()
373}
374
375/// Formats duration into a human-readable string
376///
377/// Note: this format is guaranteed to have same value when using
378/// parse_duration, but we can change some details of the exact composition
379/// of the value.
380///
381/// # Examples
382///
383/// ```
384/// use std::time::Duration;
385/// use humantime::format_duration;
386///
387/// let val1 = Duration::new(9420, 0);
388/// assert_eq!(format_duration(val1).to_string(), "2h 37m");
389/// let val2 = Duration::new(0, 32_000_000);
390/// assert_eq!(format_duration(val2).to_string(), "32ms");
391/// ```
392pub fn format_duration(val: Duration) -> FormattedDuration {
393    FormattedDuration(val)
394}
395
396fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result {
397    if value > 0 {
398        if *started {
399            f.write_str(" ")?;
400        }
401        write!(f, "{}{}", value, name)?;
402        if value > 1 {
403            f.write_str("s")?;
404        }
405        *started = true;
406    }
407    Ok(())
408}
409fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result {
410    if value > 0 {
411        if *started {
412            f.write_str(" ")?;
413        }
414        write!(f, "{}{}", value, name)?;
415        *started = true;
416    }
417    Ok(())
418}
419
420impl FormattedDuration {
421    /// Returns a reference to the [`Duration`][] that is being formatted.
422    pub fn get_ref(&self) -> &Duration {
423        &self.0
424    }
425}
426
427impl fmt::Display for FormattedDuration {
428    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
429        let secs = self.0.as_secs();
430        let nanos = self.0.subsec_nanos();
431
432        if secs == 0 && nanos == 0 {
433            f.write_str("0s")?;
434            return Ok(());
435        }
436
437        let years = secs / 31_557_600; // 365.25d
438        let ydays = secs % 31_557_600;
439        let months = ydays / 2_630_016; // 30.44d
440        let mdays = ydays % 2_630_016;
441        let days = mdays / 86400;
442        let day_secs = mdays % 86400;
443        let hours = day_secs / 3600;
444        let minutes = day_secs % 3600 / 60;
445        let seconds = day_secs % 60;
446
447        let millis = nanos / 1_000_000;
448        let micros = nanos / 1000 % 1000;
449        let nanosec = nanos % 1000;
450
451        let started = &mut false;
452        item_plural(f, started, "year", years)?;
453        item_plural(f, started, "month", months)?;
454        item_plural(f, started, "day", days)?;
455        item(f, started, "h", hours as u32)?;
456        item(f, started, "m", minutes as u32)?;
457        item(f, started, "s", seconds as u32)?;
458        item(f, started, "ms", millis)?;
459        #[cfg(feature = "mu")]
460        item(f, started, "µs", micros)?;
461        #[cfg(not(feature = "mu"))]
462        item(f, started, "us", micros)?;
463        item(f, started, "ns", nanosec)?;
464        Ok(())
465    }
466}
467
468#[cfg(test)]
469mod test {
470    use std::time::Duration;
471
472    use rand::Rng;
473
474    use super::Error;
475    use super::{format_duration, parse_duration};
476
477    #[test]
478    #[allow(clippy::cognitive_complexity)]
479    fn test_units() {
480        assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17)));
481        assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17)));
482        assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33)));
483        assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000)));
484        assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000)));
485        assert_eq!(parse_duration("163µs"), Ok(Duration::new(0, 163000)));
486        assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31_000_000)));
487        assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31_000_000)));
488        assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6_000_000)));
489        assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0)));
490        assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0)));
491        assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0)));
492        assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0)));
493        assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0)));
494        assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0)));
495        assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0)));
496        assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0)));
497        assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0)));
498        assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0)));
499        assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0)));
500        assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0)));
501        assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0)));
502        assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0)));
503        assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0)));
504        assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0)));
505        assert_eq!(parse_duration("2days"), Ok(Duration::new(172_800, 0)));
506        assert_eq!(parse_duration("365d"), Ok(Duration::new(31_536_000, 0)));
507        assert_eq!(parse_duration("1week"), Ok(Duration::new(604_800, 0)));
508        assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4_233_600, 0)));
509        assert_eq!(
510            parse_duration("104wks"),
511            Ok(Duration::new(2 * 31_449_600, 0))
512        );
513        assert_eq!(parse_duration("100wk"), Ok(Duration::new(60_480_000, 0)));
514        assert_eq!(parse_duration("52w"), Ok(Duration::new(31_449_600, 0)));
515        assert_eq!(parse_duration("1month"), Ok(Duration::new(2_630_016, 0)));
516        assert_eq!(
517            parse_duration("3months"),
518            Ok(Duration::new(3 * 2_630_016, 0))
519        );
520        assert_eq!(parse_duration("12M"), Ok(Duration::new(31_560_192, 0)));
521        assert_eq!(parse_duration("1year"), Ok(Duration::new(31_557_600, 0)));
522        assert_eq!(
523            parse_duration("7years"),
524            Ok(Duration::new(7 * 31_557_600, 0))
525        );
526        assert_eq!(
527            parse_duration("15yrs"),
528            Ok(Duration::new(15 * 31_557_600, 0))
529        );
530        assert_eq!(
531            parse_duration("10yr"),
532            Ok(Duration::new(10 * 31_557_600, 0))
533        );
534        assert_eq!(parse_duration("17y"), Ok(Duration::new(536_479_200, 0)));
535    }
536
537    #[test]
538    fn test_fractional_bad_input() {
539        assert!(matches!(
540            parse_duration("1.s"),
541            Err(Error::InvalidCharacter(_))
542        ));
543        assert!(matches!(
544            parse_duration("1..s"),
545            Err(Error::InvalidCharacter(_))
546        ));
547        assert!(matches!(
548            parse_duration(".1s"),
549            Err(Error::NumberExpected(_))
550        ));
551        assert!(matches!(parse_duration("."), Err(Error::NumberExpected(_))));
552        assert_eq!(
553            parse_duration("0.000123456789s"),
554            Err(Error::NumberOverflow)
555        );
556    }
557
558    #[test]
559    fn test_fractional_units() {
560        // nanos
561        for input in &["17.5nsec", "5.1nanos", "0.0005ns"] {
562            let bad_ns_frac = parse_duration(input);
563            assert!(
564                matches!(bad_ns_frac, Err(Error::NumberOverflow)),
565                "fractions of nanoseconds should fail, but got {bad_ns_frac:?}"
566            );
567        }
568
569        // micros
570        assert_eq!(parse_duration("3.1usec"), Ok(Duration::new(0, 3100)));
571        assert_eq!(parse_duration("3.1us"), Ok(Duration::new(0, 3100)));
572        assert_eq!(parse_duration("3.01us"), Ok(Duration::new(0, 3010)));
573        assert_eq!(parse_duration("3.001us"), Ok(Duration::new(0, 3001)));
574        for input in &["3.0001us", "0.0001us", "0.123456us"] {
575            let bad_ms_frac = parse_duration(input);
576            assert!(
577                matches!(bad_ms_frac, Err(Error::NumberOverflow)),
578                "too small fractions of microseconds should fail, but got {bad_ms_frac:?}"
579            );
580        }
581
582        // millis
583        assert_eq!(parse_duration("31.1msec"), Ok(Duration::new(0, 31_100_000)));
584        assert_eq!(
585            parse_duration("31.1millis"),
586            Ok(Duration::new(0, 31_100_000))
587        );
588        assert_eq!(parse_duration("31.1ms"), Ok(Duration::new(0, 31_100_000)));
589        assert_eq!(parse_duration("31.01ms"), Ok(Duration::new(0, 31_010_000)));
590        assert_eq!(parse_duration("31.001ms"), Ok(Duration::new(0, 31_001_000)));
591        assert_eq!(
592            parse_duration("31.0001ms"),
593            Ok(Duration::new(0, 31_000_100))
594        );
595        assert_eq!(
596            parse_duration("31.00001ms"),
597            Ok(Duration::new(0, 31_000_010))
598        );
599        assert_eq!(
600            parse_duration("31.000001ms"),
601            Ok(Duration::new(0, 31_000_001))
602        );
603        assert!(matches!(
604            parse_duration("31.0000001ms"),
605            Err(Error::NumberOverflow)
606        ));
607
608        // seconds
609        assert_eq!(parse_duration("300.0sec"), Ok(Duration::new(300, 0)));
610        assert_eq!(parse_duration("300.0secs"), Ok(Duration::new(300, 0)));
611        assert_eq!(parse_duration("300.0seconds"), Ok(Duration::new(300, 0)));
612        assert_eq!(parse_duration("300.0s"), Ok(Duration::new(300, 0)));
613        assert_eq!(parse_duration("0.0s"), Ok(Duration::new(0, 0)));
614        assert_eq!(parse_duration("0.2s"), Ok(Duration::new(0, 200_000_000)));
615        assert_eq!(parse_duration("1.2s"), Ok(Duration::new(1, 200_000_000)));
616        assert_eq!(parse_duration("1.02s"), Ok(Duration::new(1, 20_000_000)));
617        assert_eq!(parse_duration("1.002s"), Ok(Duration::new(1, 2_000_000)));
618        assert_eq!(parse_duration("1.0002s"), Ok(Duration::new(1, 200_000)));
619        assert_eq!(parse_duration("1.00002s"), Ok(Duration::new(1, 20_000)));
620        assert_eq!(parse_duration("1.000002s"), Ok(Duration::new(1, 2_000)));
621        assert_eq!(parse_duration("1.0000002s"), Ok(Duration::new(1, 200)));
622        assert_eq!(parse_duration("1.00000002s"), Ok(Duration::new(1, 20)));
623        assert_eq!(parse_duration("1.000000002s"), Ok(Duration::new(1, 2)));
624        assert_eq!(
625            parse_duration("1.123456789s"),
626            Ok(Duration::new(1, 123_456_789))
627        );
628        assert!(matches!(
629            parse_duration("1.0000000002s"),
630            Err(Error::NumberOverflow)
631        ));
632        assert!(matches!(
633            parse_duration("0.0000000002s"),
634            Err(Error::NumberOverflow)
635        ));
636
637        // minutes
638        assert_eq!(parse_duration("100.0m"), Ok(Duration::new(6000, 0)));
639        assert_eq!(parse_duration("12.1min"), Ok(Duration::new(726, 0)));
640        assert_eq!(parse_duration("12.1mins"), Ok(Duration::new(726, 0)));
641        assert_eq!(parse_duration("1.5minute"), Ok(Duration::new(90, 0)));
642        assert_eq!(parse_duration("1.5minutes"), Ok(Duration::new(90, 0)));
643
644        // hours
645        assert_eq!(parse_duration("2.0h"), Ok(Duration::new(7200, 0)));
646        assert_eq!(parse_duration("2.0hr"), Ok(Duration::new(7200, 0)));
647        assert_eq!(parse_duration("2.0hrs"), Ok(Duration::new(7200, 0)));
648        assert_eq!(parse_duration("2.0hours"), Ok(Duration::new(7200, 0)));
649        assert_eq!(parse_duration("2.5h"), Ok(Duration::new(9000, 0)));
650        assert_eq!(parse_duration("0.5h"), Ok(Duration::new(1800, 0)));
651
652        // days
653        assert_eq!(
654            parse_duration("1.5day"),
655            Ok(Duration::new(86400 + 86400 / 2, 0))
656        );
657        assert_eq!(
658            parse_duration("1.5days"),
659            Ok(Duration::new(86400 + 86400 / 2, 0))
660        );
661        assert_eq!(
662            parse_duration("1.5d"),
663            Ok(Duration::new(86400 + 86400 / 2, 0))
664        );
665        assert!(matches!(
666            parse_duration("0.00000005d"),
667            Err(Error::NumberOverflow)
668        ));
669    }
670
671    #[test]
672    fn test_fractional_combined() {
673        assert_eq!(parse_duration("7.120us 3ns"), Ok(Duration::new(0, 7123)));
674        assert_eq!(parse_duration("7.123us 4ns"), Ok(Duration::new(0, 7127)));
675        assert_eq!(
676            parse_duration("1.234s 789ns"),
677            Ok(Duration::new(1, 234_000_789))
678        );
679        assert_eq!(
680            parse_duration("1.234s 0.789us"),
681            Ok(Duration::new(1, 234_000_789))
682        );
683        assert_eq!(
684            parse_duration("1.234567s 0.789us"),
685            Ok(Duration::new(1, 234_567_789))
686        );
687        assert_eq!(
688            parse_duration("1.234s 1.345ms 1.678us 1ns"),
689            Ok(Duration::new(1, 235_346_679))
690        );
691        assert_eq!(
692            parse_duration("1.234s 0.345ms 0.678us 0ns"),
693            Ok(Duration::new(1, 234_345_678))
694        );
695        assert_eq!(
696            parse_duration("1.234s0.345ms0.678us0ns"),
697            Ok(Duration::new(1, 234_345_678))
698        );
699    }
700
701    #[test]
702    fn allow_0_with_no_unit() {
703        assert_eq!(parse_duration("0"), Ok(Duration::new(0, 0)));
704    }
705
706    #[test]
707    fn test_combo() {
708        assert_eq!(
709            parse_duration("20 min 17 nsec "),
710            Ok(Duration::new(1200, 17))
711        );
712        assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0)));
713    }
714
715    #[test]
716    fn all_86400_seconds() {
717        for second in 0..86400 {
718            // scan leap year and non-leap year
719            let d = Duration::new(second, 0);
720            assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap());
721        }
722    }
723
724    #[test]
725    fn random_second() {
726        for _ in 0..10000 {
727            let sec = rand::rng().random_range(0..253_370_764_800);
728            let d = Duration::new(sec, 0);
729            assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap());
730        }
731    }
732
733    #[test]
734    fn random_any() {
735        for _ in 0..10000 {
736            let sec = rand::rng().random_range(0..253_370_764_800);
737            let nanos = rand::rng().random_range(0..1_000_000_000);
738            let d = Duration::new(sec, nanos);
739            assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap());
740        }
741    }
742
743    #[test]
744    fn test_overlow() {
745        // Overflow on subseconds is earlier because of how we do conversion
746        // we could fix it, but I don't see any good reason for this
747        assert_eq!(
748            parse_duration("100000000000000000000ns"),
749            Err(Error::NumberOverflow)
750        );
751        assert_eq!(
752            parse_duration("100000000000000000us"),
753            Err(Error::NumberOverflow)
754        );
755        assert_eq!(
756            parse_duration("100000000000000ms"),
757            Err(Error::NumberOverflow)
758        );
759
760        assert_eq!(
761            parse_duration("100000000000000000000s"),
762            Err(Error::NumberOverflow)
763        );
764        assert_eq!(
765            parse_duration("10000000000000000000m"),
766            Err(Error::NumberOverflow)
767        );
768        assert_eq!(
769            parse_duration("1000000000000000000h"),
770            Err(Error::NumberOverflow)
771        );
772        assert_eq!(
773            parse_duration("100000000000000000d"),
774            Err(Error::NumberOverflow)
775        );
776        assert_eq!(
777            parse_duration("10000000000000000w"),
778            Err(Error::NumberOverflow)
779        );
780        assert_eq!(
781            parse_duration("1000000000000000M"),
782            Err(Error::NumberOverflow)
783        );
784        assert_eq!(
785            parse_duration("10000000000000y"),
786            Err(Error::NumberOverflow)
787        );
788    }
789
790    #[test]
791    fn test_nice_error_message() {
792        assert_eq!(
793            parse_duration("123").unwrap_err().to_string(),
794            "time unit needed, for example 123sec or 123ms"
795        );
796        assert_eq!(
797            parse_duration("10 months 1").unwrap_err().to_string(),
798            "time unit needed, for example 1sec or 1ms"
799        );
800        assert_eq!(
801            parse_duration("10nights").unwrap_err().to_string(),
802            "unknown time unit \"nights\", supported units: \
803            ns, us/µs, ms, sec, min, hours, days, weeks, months, \
804            years (and few variations)"
805        );
806    }
807
808    #[cfg(feature = "mu")]
809    #[test]
810    fn test_format_micros() {
811        assert_eq!(
812            format_duration(Duration::from_micros(123)).to_string(),
813            "123µs"
814        );
815    }
816
817    #[cfg(not(feature = "mu"))]
818    #[test]
819    fn test_format_micros() {
820        assert_eq!(
821            format_duration(Duration::from_micros(123)).to_string(),
822            "123us"
823        );
824    }
825
826    #[test]
827    fn test_error_cases() {
828        assert_eq!(
829            parse_duration("\0").unwrap_err().to_string(),
830            "expected number at 0"
831        );
832        assert_eq!(
833            parse_duration("\r").unwrap_err().to_string(),
834            "value was empty"
835        );
836        assert_eq!(
837            parse_duration("1~").unwrap_err().to_string(),
838            "invalid character at 1"
839        );
840        assert_eq!(
841            parse_duration("1Nå").unwrap_err().to_string(),
842            "invalid character at 2"
843        );
844        assert_eq!(parse_duration("222nsec221nanosmsec7s5msec572s").unwrap_err().to_string(),
845                   "unknown time unit \"nanosmsec\", supported units: ns, us/µs, ms, sec, min, hours, days, weeks, months, years (and few variations)");
846    }
847}