use std::time::Duration;
const UNITS: [(&str, u128); 9] = [
("y", 365 * 24 * 60 * 60 * 1_000_000_000),
("mo", 30 * 24 * 60 * 60 * 1_000_000_000),
("d", 24 * 60 * 60 * 1_000_000_000),
("h", 60 * 60 * 1_000_000_000),
("m", 60 * 1_000_000_000),
("s", 1_000_000_000),
("ms", 1_000_000),
("µs", 1_000),
("ns", 1),
];
const NANOS_PER_MILLI: f64 = 1_000_000.0;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum DurationFormat {
Parts(usize),
#[allow(dead_code)]
Millis { decimals: usize },
}
impl DurationFormat {
pub(crate) const fn default_parts() -> Self {
Self::Parts(super::bytes::DEFAULT_PARTS)
}
}
pub(crate) fn format_duration(duration: Duration, fmt: &DurationFormat) -> String {
match *fmt {
DurationFormat::Parts(parts) => composite(duration.as_nanos(), parts),
DurationFormat::Millis { decimals } => {
let millis = duration.as_nanos() as f64 / NANOS_PER_MILLI;
format!("{millis:.decimals$}ms")
}
}
}
fn composite(nanos: u128, parts: usize) -> String {
let wanted = parts.max(1);
let mut remainder = nanos;
let mut out: Vec<String> = Vec::with_capacity(wanted);
for (unit, scale) in UNITS {
if out.len() == wanted {
break;
}
let count = remainder / scale;
if count > 0 {
out.push(format!("{count}{unit}"));
remainder %= scale;
}
}
if out.is_empty() {
return "0s".to_string();
}
out.join(" ")
}
#[cfg(test)]
#[path = "duration_tests.rs"]
mod tests;