1use std::fmt::{self, Display, Formatter, Write};
2use std::time::Duration;
3
4use super::round_with_precision;
5
6pub fn format_duration(duration: Duration) -> impl Display {
8 DurationDisplay(duration)
9}
10
11struct DurationDisplay(Duration);
13
14impl Display for DurationDisplay {
15 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
16 let mut space = false;
17 macro_rules! piece {
18 ($($tts:tt)*) => {
19 if std::mem::replace(&mut space, true) {
20 f.write_char(' ')?;
21 }
22 write!(f, $($tts)*)?;
23 };
24 }
25
26 let secs = self.0.as_secs();
27 let (mins, secs) = (secs / 60, (secs % 60));
28 let (hours, mins) = (mins / 60, (mins % 60));
29 let (days, hours) = ((hours / 24), (hours % 24));
30
31 if days > 0 {
32 piece!("{days} d");
33 }
34
35 if hours > 0 {
36 piece!("{hours} h");
37 }
38
39 if mins > 0 {
40 piece!("{mins} min");
41 }
42
43 if days > 0 || hours > 0 {
45 return Ok(());
46 }
47
48 let order = |exp| 1000u64.pow(exp);
49 let nanos = secs * order(3) + self.0.subsec_nanos() as u64;
50 let fract = |exp| round_with_precision(nanos as f64 / order(exp) as f64, 2);
51
52 if nanos == 0 || self.0 > Duration::from_secs(1) {
53 if self.0 > Duration::from_secs(300) {
55 piece!("{secs} s");
56 } else {
57 piece!("{} s", fract(3));
58 }
59 } else if self.0 > Duration::from_millis(1) {
60 piece!("{} ms", fract(2));
61 } else if self.0 > Duration::from_micros(1) {
62 piece!("{} µs", fract(1));
63 } else {
64 piece!("{} ns", fract(0));
65 }
66
67 Ok(())
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[track_caller]
76 fn test(duration: Duration, expected: &str) {
77 assert_eq!(format_duration(duration).to_string(), expected);
78 }
79
80 #[test]
81 fn test_format_duration() {
82 test(Duration::from_secs(1000000), "11 d 13 h 46 min");
83 test(Duration::from_secs(3600 * 24), "1 d");
84 test(Duration::from_secs(3600), "1 h");
85 test(Duration::from_secs(3600 + 240), "1 h 4 min");
86 test(Duration::from_secs_f64(364.77), "6 min 4 s");
87 test(Duration::from_secs_f64(264.776), "4 min 24.78 s");
88 test(Duration::from_secs(3), "3 s");
89 test(Duration::from_secs_f64(2.8492), "2.85 s");
90 test(Duration::from_micros(734), "734 µs");
91 test(Duration::from_micros(294816), "294.82 ms");
92 test(Duration::from_nanos(1), "1 ns");
93 }
94}