use std::fmt::{self, Display, Formatter, Write};
use std::time::Duration;
use super::round_with_precision;
pub fn format_duration(duration: Duration) -> impl Display {
DurationDisplay(duration)
}
struct DurationDisplay(Duration);
impl Display for DurationDisplay {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut space = false;
macro_rules! piece {
($($tts:tt)*) => {
if std::mem::replace(&mut space, true) {
f.write_char(' ')?;
}
write!(f, $($tts)*)?;
};
}
let secs = self.0.as_secs();
let (mins, secs) = (secs / 60, (secs % 60));
let (hours, mins) = (mins / 60, (mins % 60));
let (days, hours) = ((hours / 24), (hours % 24));
if days > 0 {
piece!("{days} d");
}
if hours > 0 {
piece!("{hours} h");
}
if mins > 0 {
piece!("{mins} min");
}
if days > 0 || hours > 0 {
return Ok(());
}
let order = |exp| 1000u64.pow(exp);
let nanos = secs * order(3) + self.0.subsec_nanos() as u64;
let fract = |exp| round_with_precision(nanos as f64 / order(exp) as f64, 2);
if nanos == 0 || self.0 > Duration::from_secs(1) {
if self.0 > Duration::from_secs(300) {
piece!("{secs} s");
} else {
piece!("{} s", fract(3));
}
} else if self.0 > Duration::from_millis(1) {
piece!("{} ms", fract(2));
} else if self.0 > Duration::from_micros(1) {
piece!("{} µs", fract(1));
} else {
piece!("{} ns", fract(0));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn test(duration: Duration, expected: &str) {
assert_eq!(format_duration(duration).to_string(), expected);
}
#[test]
fn test_format_duration() {
test(Duration::from_secs(1000000), "11 d 13 h 46 min");
test(Duration::from_secs(3600 * 24), "1 d");
test(Duration::from_secs(3600), "1 h");
test(Duration::from_secs(3600 + 240), "1 h 4 min");
test(Duration::from_secs_f64(364.77), "6 min 4 s");
test(Duration::from_secs_f64(264.776), "4 min 24.78 s");
test(Duration::from_secs(3), "3 s");
test(Duration::from_secs_f64(2.8492), "2.85 s");
test(Duration::from_micros(734), "734 µs");
test(Duration::from_micros(294816), "294.82 ms");
test(Duration::from_nanos(1), "1 ns");
}
}