diff options
Diffstat (limited to 'crates/typst-utils/src/duration.rs')
| -rw-r--r-- | crates/typst-utils/src/duration.rs | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/crates/typst-utils/src/duration.rs b/crates/typst-utils/src/duration.rs new file mode 100644 index 00000000..88348248 --- /dev/null +++ b/crates/typst-utils/src/duration.rs @@ -0,0 +1,94 @@ +use std::fmt::{self, Display, Formatter, Write}; +use std::time::Duration; + +use super::round_with_precision; + +/// Formats a duration with a precision suitable for human display. +pub fn format_duration(duration: Duration) -> impl Display { + DurationDisplay(duration) +} + +/// Displays a `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"); + } + + // No need to display anything more than minutes at this point. + 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) { + // For durations > 5 min, we drop the fractional part. + 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"); + } +} |
