Coverage Report

Created: 2025-11-28 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/humantime-2.3.0/src/duration.rs
Line
Count
Source
1
use std::error::Error as StdError;
2
use std::fmt;
3
use std::str::{Chars, FromStr};
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 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
56
impl StdError for Error {}
57
58
impl fmt::Display for Error {
59
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60
0
        match self {
61
0
            Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset),
62
0
            Error::NumberExpected(offset) => write!(f, "expected number at {}", offset),
63
0
            Error::UnknownUnit { unit, value, .. } if unit.is_empty() => {
64
0
                write!(f, "time unit needed, for example {0}sec or {0}ms", value)
65
            }
66
0
            Error::UnknownUnit { unit, .. } => {
67
0
                write!(
68
0
                    f,
69
0
                    "unknown time unit {:?}, \
70
0
                    supported units: ns, us/µs, ms, sec, min, hours, days, \
71
0
                    weeks, months, years (and few variations)",
72
                    unit
73
                )
74
            }
75
0
            Error::NumberOverflow => write!(f, "number is too large or cannot be represented without a lack of precision (values below 1ns are not supported)"),
76
0
            Error::Empty => write!(f, "value was empty"),
77
        }
78
0
    }
79
}
80
81
/// A wrapper type that allows you to Display a Duration
82
#[derive(Debug, Clone)]
83
pub struct FormattedDuration(Duration);
84
85
trait 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
91
impl OverflowOp for u64 {
92
0
    fn mul(self, other: Self) -> Result<Self, Error> {
93
0
        self.checked_mul(other).ok_or(Error::NumberOverflow)
94
0
    }
95
0
    fn add(self, other: Self) -> Result<Self, Error> {
96
0
        self.checked_add(other).ok_or(Error::NumberOverflow)
97
0
    }
98
0
    fn div(self, other: Self) -> Result<Self, Error> {
99
0
        match self % other {
100
0
            0 => Ok(self / other),
101
0
            _ => Err(Error::NumberOverflow),
102
        }
103
0
    }
104
}
105
106
#[derive(Debug, Clone, Copy)]
107
struct Fraction {
108
    numerator: u64,
109
    denominator: u64,
110
}
111
112
struct Parser<'a> {
113
    iter: Chars<'a>,
114
    src: &'a str,
115
}
116
117
impl Parser<'_> {
118
0
    fn parse(mut self) -> Result<Duration, Error> {
119
0
        let mut n = self.parse_first_char()?.ok_or(Error::Empty)?; // integer part
120
0
        let mut out = Duration::ZERO;
121
        'outer: loop {
122
0
            let mut frac = None; // fractional part
123
0
            let mut off = self.off();
124
0
            while let Some(c) = self.iter.next() {
125
0
                match c {
126
0
                    '0'..='9' => {
127
0
                        n = n
128
0
                            .checked_mul(10)
129
0
                            .and_then(|x| x.checked_add(c as u64 - '0' as u64))
130
0
                            .ok_or(Error::NumberOverflow)?;
131
                    }
132
0
                    c if c.is_whitespace() => {}
133
0
                    'a'..='z' | 'A'..='Z' | 'µ' => {
134
0
                        break;
135
                    }
136
                    '.' => {
137
                        // decimal separator, the fractional part begins now
138
0
                        frac = Some(self.parse_fractional_part(&mut off)?);
139
0
                        break;
140
                    }
141
                    _ => {
142
0
                        return Err(Error::InvalidCharacter(off));
143
                    }
144
                }
145
0
                off = self.off();
146
            }
147
0
            let start = off;
148
0
            let mut off = self.off();
149
0
            while let Some(c) = self.iter.next() {
150
0
                match c {
151
0
                    '0'..='9' => {
152
0
                        self.parse_unit(n, frac, start, off, &mut out)?;
153
0
                        n = c as u64 - '0' as u64;
154
0
                        continue 'outer;
155
                    }
156
0
                    c if c.is_whitespace() => break,
157
0
                    'a'..='z' | 'A'..='Z' | 'µ' => {}
158
                    _ => {
159
0
                        return Err(Error::InvalidCharacter(off));
160
                    }
161
                }
162
0
                off = self.off();
163
            }
164
165
0
            self.parse_unit(n, frac, start, off, &mut out)?;
166
0
            n = match self.parse_first_char()? {
167
0
                Some(n) => n,
168
0
                None => return Ok(out),
169
            };
170
        }
171
0
    }
172
173
0
    fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
174
0
        let off = self.off();
175
0
        for c in self.iter.by_ref() {
176
0
            match c {
177
0
                '0'..='9' => {
178
0
                    return Ok(Some(c as u64 - '0' as u64));
179
                }
180
0
                c if c.is_whitespace() => continue,
181
                _ => {
182
0
                    return Err(Error::NumberExpected(off));
183
                }
184
            }
185
        }
186
0
        Ok(None)
187
0
    }
188
189
0
    fn parse_fractional_part(&mut self, off: &mut usize) -> Result<Fraction, Error> {
190
0
        let mut numerator = 0u64;
191
0
        let mut denominator = 1u64;
192
0
        let mut zeros = true;
193
0
        while let Some(c) = self.iter.next() {
194
0
            match c {
195
                '0' => {
196
0
                    denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?;
197
0
                    if !zeros {
198
0
                        numerator = numerator.checked_mul(10).ok_or(Error::NumberOverflow)?;
199
0
                    }
200
                }
201
0
                '1'..='9' => {
202
0
                    zeros = false;
203
0
                    denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?;
204
0
                    numerator = numerator
205
0
                        .checked_mul(10)
206
0
                        .and_then(|x| x.checked_add(c as u64 - '0' as u64))
207
0
                        .ok_or(Error::NumberOverflow)?;
208
                }
209
0
                c if c.is_whitespace() => {}
210
0
                'a'..='z' | 'A'..='Z' | 'µ' => {
211
0
                    break;
212
                }
213
                _ => {
214
0
                    return Err(Error::InvalidCharacter(*off));
215
                }
216
            };
217
            // update the offset used by the parsing loop
218
0
            *off = self.off();
219
        }
220
0
        if denominator == 1 {
221
            // no digits were given after the separator, e.g. "1."
222
0
            return Err(Error::InvalidCharacter(*off));
223
0
        }
224
0
        Ok(Fraction {
225
0
            numerator,
226
0
            denominator,
227
0
        })
228
0
    }
229
230
0
    fn off(&self) -> usize {
231
0
        self.src.len() - self.iter.as_str().len()
232
0
    }
233
234
0
    fn parse_unit(
235
0
        &mut self,
236
0
        n: u64,
237
0
        frac: Option<Fraction>,
238
0
        start: usize,
239
0
        end: usize,
240
0
        out: &mut Duration,
241
0
    ) -> Result<(), Error> {
242
0
        let unit = match Unit::from_str(&self.src[start..end]) {
243
0
            Ok(u) => u,
244
            Err(()) => {
245
0
                return Err(Error::UnknownUnit {
246
0
                    start,
247
0
                    end,
248
0
                    unit: self.src[start..end].to_owned(),
249
0
                    value: n,
250
0
                });
251
            }
252
        };
253
254
        // add the integer part
255
0
        let (sec, nsec) = match unit {
256
0
            Unit::Nanosecond => (0u64, n),
257
0
            Unit::Microsecond => (0u64, n.mul(1000)?),
258
0
            Unit::Millisecond => (0u64, n.mul(1_000_000)?),
259
0
            Unit::Second => (n, 0),
260
0
            Unit::Minute => (n.mul(60)?, 0),
261
0
            Unit::Hour => (n.mul(3600)?, 0),
262
0
            Unit::Day => (n.mul(86400)?, 0),
263
0
            Unit::Week => (n.mul(86400 * 7)?, 0),
264
0
            Unit::Month => (n.mul(2_630_016)?, 0), // 30.44d
265
0
            Unit::Year => (n.mul(31_557_600)?, 0), // 365.25d
266
        };
267
0
        add_current(sec, nsec, out)?;
268
269
        // add the fractional part
270
        if let Some(Fraction {
271
0
            numerator: n,
272
0
            denominator: d,
273
0
        }) = frac
274
        {
275
0
            let (sec, nsec) = match unit {
276
0
                Unit::Nanosecond => return Err(Error::NumberOverflow),
277
0
                Unit::Microsecond => (0, n.mul(1000)?.div(d)?),
278
0
                Unit::Millisecond => (0, n.mul(1_000_000)?.div(d)?),
279
0
                Unit::Second => (0, n.mul(1_000_000_000)?.div(d)?),
280
0
                Unit::Minute => (0, n.mul(60_000_000_000)?.div(d)?),
281
0
                Unit::Hour => (n.mul(3600)?.div(d)?, 0),
282
0
                Unit::Day => (n.mul(86400)?.div(d)?, 0),
283
0
                Unit::Week => (n.mul(86400 * 7)?.div(d)?, 0),
284
0
                Unit::Month => (n.mul(2_630_016)?.div(d)?, 0), // 30.44d
285
0
                Unit::Year => (n.mul(31_557_600)?.div(d)?, 0), // 365.25d
286
            };
287
0
            add_current(sec, nsec, out)?;
288
0
        }
289
290
0
        Ok(())
291
0
    }
292
}
293
294
0
fn add_current(mut sec: u64, nsec: u64, out: &mut Duration) -> Result<(), Error> {
295
0
    let mut nsec = (out.subsec_nanos() as u64).add(nsec)?;
296
0
    if nsec > 1_000_000_000 {
297
0
        sec = sec.add(nsec / 1_000_000_000)?;
298
0
        nsec %= 1_000_000_000;
299
0
    }
300
0
    sec = out.as_secs().add(sec)?;
301
0
    *out = Duration::new(sec, nsec as u32);
302
0
    Ok(())
303
0
}
304
305
enum Unit {
306
    Nanosecond,
307
    Microsecond,
308
    Millisecond,
309
    Second,
310
    Minute,
311
    Hour,
312
    Day,
313
    Week,
314
    Month,
315
    Year,
316
}
317
318
impl FromStr for Unit {
319
    type Err = ();
320
321
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
322
0
        match s {
323
0
            "nanos" | "nsec" | "ns" => Ok(Self::Nanosecond),
324
0
            "usec" | "us" | "µs" => Ok(Self::Microsecond),
325
0
            "millis" | "msec" | "ms" => Ok(Self::Millisecond),
326
0
            "seconds" | "second" | "secs" | "sec" | "s" => Ok(Self::Second),
327
0
            "minutes" | "minute" | "min" | "mins" | "m" => Ok(Self::Minute),
328
0
            "hours" | "hour" | "hr" | "hrs" | "h" => Ok(Self::Hour),
329
0
            "days" | "day" | "d" => Ok(Self::Day),
330
0
            "weeks" | "week" | "wk" | "wks" | "w" => Ok(Self::Week),
331
0
            "months" | "month" | "M" => Ok(Self::Month),
332
0
            "years" | "year" | "yr" | "yrs" | "y" => Ok(Self::Year),
333
0
            _ => Err(()),
334
        }
335
0
    }
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
/// ```
364
0
pub fn parse_duration(s: &str) -> Result<Duration, Error> {
365
0
    if s == "0" {
366
0
        return Ok(Duration::ZERO);
367
0
    }
368
0
    Parser {
369
0
        iter: s.chars(),
370
0
        src: s,
371
0
    }
372
0
    .parse()
373
0
}
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
/// ```
392
0
pub fn format_duration(val: Duration) -> FormattedDuration {
393
0
    FormattedDuration(val)
394
0
}
395
396
0
fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result {
397
0
    if value > 0 {
398
0
        if *started {
399
0
            f.write_str(" ")?;
400
0
        }
401
0
        write!(f, "{}{}", value, name)?;
402
0
        if value > 1 {
403
0
            f.write_str("s")?;
404
0
        }
405
0
        *started = true;
406
0
    }
407
0
    Ok(())
408
0
}
409
0
fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result {
410
0
    if value > 0 {
411
0
        if *started {
412
0
            f.write_str(" ")?;
413
0
        }
414
0
        write!(f, "{}{}", value, name)?;
415
0
        *started = true;
416
0
    }
417
0
    Ok(())
418
0
}
419
420
impl FormattedDuration {
421
    /// Returns a reference to the [`Duration`][] that is being formatted.
422
0
    pub fn get_ref(&self) -> &Duration {
423
0
        &self.0
424
0
    }
425
}
426
427
impl fmt::Display for FormattedDuration {
428
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
429
0
        let secs = self.0.as_secs();
430
0
        let nanos = self.0.subsec_nanos();
431
432
0
        if secs == 0 && nanos == 0 {
433
0
            f.write_str("0s")?;
434
0
            return Ok(());
435
0
        }
436
437
0
        let years = secs / 31_557_600; // 365.25d
438
0
        let ydays = secs % 31_557_600;
439
0
        let months = ydays / 2_630_016; // 30.44d
440
0
        let mdays = ydays % 2_630_016;
441
0
        let days = mdays / 86400;
442
0
        let day_secs = mdays % 86400;
443
0
        let hours = day_secs / 3600;
444
0
        let minutes = day_secs % 3600 / 60;
445
0
        let seconds = day_secs % 60;
446
447
0
        let millis = nanos / 1_000_000;
448
0
        let micros = nanos / 1000 % 1000;
449
0
        let nanosec = nanos % 1000;
450
451
0
        let started = &mut false;
452
0
        item_plural(f, started, "year", years)?;
453
0
        item_plural(f, started, "month", months)?;
454
0
        item_plural(f, started, "day", days)?;
455
0
        item(f, started, "h", hours as u32)?;
456
0
        item(f, started, "m", minutes as u32)?;
457
0
        item(f, started, "s", seconds as u32)?;
458
0
        item(f, started, "ms", millis)?;
459
        #[cfg(feature = "mu")]
460
        item(f, started, "µs", micros)?;
461
        #[cfg(not(feature = "mu"))]
462
0
        item(f, started, "us", micros)?;
463
0
        item(f, started, "ns", nanosec)?;
464
0
        Ok(())
465
0
    }
466
}
467
468
#[cfg(test)]
469
mod 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
}