human_repr/
human_duration.rs

1use super::{HumanDuration, HumanDurationData};
2use crate::utils::{self, SPACE};
3use std::{fmt, time::Duration};
4
5const SPEC: &[(f64, f64, &str, usize)] = &[
6    (1e3, 1e3, "ns", 1),
7    (1e3, 1e3, "µs", 1), // uses non-ASCII “µs” suffix.
8    (1e3, 1e3, "ms", 1),
9    (60., 1., "s", 2),
10    // 1:01.1 (minutes in code, 1 decimal).
11    // 1:01:01 (hours in code, 0 decimal).
12];
13
14impl fmt::Display for HumanDurationData {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        let HumanDurationData { mut val } = self;
17        val *= 1e9;
18        for &(size, next, scale, dec) in SPEC {
19            match utils::rounded(val, dec) {
20                r if r.abs() >= size => val /= next,
21                r if r.fract() == 0. => return write!(f, "{:.0}{}{}", r, SPACE, scale),
22                r if (r * 10.).fract() == 0. => return write!(f, "{:.1}{}{}", r, SPACE, scale),
23                r => return write!(f, "{:.2}{}{}", r, SPACE, scale),
24            }
25        }
26
27        val = utils::rounded(val, 1);
28        let (m, s) = (val / 60., val % 60.);
29        match m < 60. {
30            true => match s {
31                _ if s.fract() == 0. => write!(f, "{}:{:02}", m.trunc(), s),
32                _ => write!(f, "{}:{:04}", m.trunc(), utils::rounded(s, 1)),
33            },
34            false => write!(
35                f,
36                "{}:{:02}:{:02}",
37                (m / 60.).trunc(),
38                (m % 60.).trunc(),
39                s.trunc()
40            ),
41        }
42    }
43}
44
45impl fmt::Debug for HumanDurationData {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.debug_struct("HumanDuration")
48            .field("val", &self.val)
49            .finish()?;
50        write!(f, " -> ")?;
51        fmt::Display::fmt(self, f)
52    }
53}
54
55impl PartialEq<HumanDurationData> for &str {
56    fn eq(&self, other: &HumanDurationData) -> bool {
57        utils::display_compare(self, other)
58    }
59}
60
61impl PartialEq<&str> for HumanDurationData {
62    fn eq(&self, other: &&str) -> bool {
63        other == self
64    }
65}
66
67impl From<Duration> for HumanDurationData {
68    fn from(d: Duration) -> Self {
69        d.as_secs_f64().human_duration()
70    }
71}
72
73impl HumanDuration for Duration {
74    fn human_duration(self) -> HumanDurationData {
75        self.into()
76    }
77}
78
79#[cfg(all(test, not(any(feature = "1024", feature = "iec", feature = "space"))))]
80mod tests {
81    use crate::HumanDuration;
82
83    #[test]
84    fn operation() {
85        assert_eq!("1s", 1.human_duration());
86        assert_eq!("-1s", (-1).human_duration());
87        assert_eq!("1.2ns", 0.00000000123.human_duration());
88        assert_eq!("1.8ns", 0.0000000018.human_duration());
89        assert_eq!("1µs", 0.000001.human_duration());
90        assert_eq!("-1µs", (-0.000001).human_duration());
91        assert_eq!("1µs", 0.000000999996.human_duration());
92        assert_eq!("10µs", 0.00001.human_duration());
93        assert_eq!("15.6µs", 0.0000156.human_duration());
94        assert_eq!("10ms", 0.01.human_duration());
95        assert_eq!("14.1ms", 0.0141233333333.human_duration());
96        assert_eq!("1ms", 0.000999999.human_duration());
97        assert_eq!("20ms", 0.0199999.human_duration());
98        assert_eq!("110ms", 0.1099999.human_duration());
99        assert_eq!("160ms", 0.1599999.human_duration());
100        assert_eq!("801.5ms", 0.8015.human_duration());
101        assert_eq!("3.43s", 3.434999.human_duration());
102        assert_eq!("3.44s", 3.435999.human_duration());
103        assert_eq!("59s", 59.0.human_duration());
104        assert_eq!("59.9s", 59.9.human_duration());
105        assert_eq!("59.99s", 59.99.human_duration());
106        assert_eq!("1:00", 59.995.human_duration());
107        assert_eq!("1:08.1", 68.09.human_duration());
108        assert_eq!("2:05.8", 125.825.human_duration());
109        assert_eq!("19:20.4", 1160.36.human_duration());
110        assert_eq!("1:04:48", 3888.395.human_duration());
111        assert_eq!("2:46:40", 10000u16.human_duration());
112        assert_eq!("27:46:40", 100000i64.human_duration());
113        assert_eq!("277:46:40", 1000000isize.human_duration());
114    }
115
116    #[test]
117    fn flexibility() {
118        use crate::HumanDuration;
119        use std::time::Duration;
120        macro_rules! d {
121            {$f:literal} => {
122                Duration::from_secs_f64($f)
123            };
124            {$s:literal, $n:literal} => {
125                Duration::new($s, $n)
126            };
127        }
128
129        assert_eq!("1s", d!(1.).human_duration());
130        assert_eq!("1.5s", d!(1.5).human_duration());
131        assert_eq!("1ns", d!(0.00000000123).human_duration());
132        assert_eq!("2ns", d!(0.00000000185).human_duration());
133        assert_eq!("1ns", d!(0, 1).human_duration());
134        assert_eq!("1µs", d!(0.000000999999999).human_duration());
135        assert_eq!("1µs", d!(0, 1000).human_duration());
136        assert_eq!("10µs", d!(0, 10000).human_duration());
137        assert_eq!("15.6µs", d!(0, 15600).human_duration());
138        assert_eq!("10ms", d!(0.01).human_duration());
139        assert_eq!("14.1ms", d!(0.0141233333333).human_duration());
140        assert_eq!("110ms", d!(0, 110000000).human_duration());
141        assert_eq!("801.5ms", d!(0.8015).human_duration());
142        assert_eq!("3.43s", d!(3.434999).human_duration());
143        assert_eq!("59s", d!(59.0).human_duration());
144        assert_eq!("59.9s", d!(59.9).human_duration());
145        assert_eq!("59.99s", d!(59.99).human_duration());
146        assert_eq!("1:00", d!(60, 0).human_duration());
147        assert_eq!("1:08.1", d!(68.09).human_duration());
148        assert_eq!("19:20.4", d!(1160, 350000000).human_duration());
149        assert_eq!("1:04:48", d!(3888.395).human_duration());
150        assert_eq!("2:46:40", d!(10000.).human_duration());
151        assert_eq!("27:46:40", d!(100000.).human_duration());
152        assert_eq!("277:46:40", d!(1000000, 1).human_duration());
153    }
154
155    #[test]
156    #[allow(clippy::needless_borrow)]
157    fn ownership() {
158        let mut a = 0.01;
159        assert_eq!("10ms", a.human_duration());
160        assert_eq!("10ms", (&a).human_duration());
161        assert_eq!("10ms", (&mut a).human_duration());
162    }
163
164    #[test]
165    fn symmetric() {
166        assert_eq!(1.human_duration(), "1s");
167    }
168}
169
170#[test]
171#[cfg(feature = "serde")]
172fn serialize() -> Result<(), serde_json::Error> {
173    use HumanDuration;
174    let h = 123456.human_duration();
175    let ser = serde_json::to_string(&h)?;
176    assert_eq!(r#"{"val":123456.0}"#, &ser);
177    let h2 = serde_json::from_str::<HumanDurationData>(&ser)?;
178    assert_eq!(h, h2);
179    Ok(())
180}