Repository: typst/typst
File: crates/typst-utils/src/format.rs (Rust)

Code before:
    let (hours, mins) = (mins / 60, (mins % 60) as u8);
    let (days, hours) = ((hours / 24) as u16, (hours % 24) as u8);
    (days, hours, mins, sec, millis, micros)
}

/// Format string using `days`, `hours`, `minutes`, `seconds`.
fn format_dhms(days: u16, hours: u8, minutes: u8, seconds: u8) -> String {
    match (days, hours, minutes, seconds) {
        (0, 0, 0, s) => format!("{s:2} s"),
        (0, 0, m, s) => format!("{m:2} m {s:2} s"),
        (0, h, m, s) => format!("{h:2} h {m:2} m {s:2} s"),
        (d, h, m, s) => format!("{d:3} d {h:2} h {m:2} m {s:2} s"),
    }
}

/// Format string starting with number of days and going bigger from there.
pub fn time_starting_with_seconds(duration: &Duration) -> String {
    let (days, hours, minutes, seconds, _, _) = get_duration_parts(duration);
    format_dhms(days, hours, minutes, seconds)
}

/// Format string starting with number of milliseconds and going bigger from there.
/// `precision` is how many digits of microseconds from floating point to the right
/// will be preserved. Note that this function will always remove all trailing zeros
/// for microseconds.
pub fn time_starting_with_ms_with_precision(
    duration: &Duration,
    precision: u8,
) -> String {
    let (d, h, m, s, ms, mcs) = get_duration_parts(duration);
    match (d, h, m, s) {
        (0, 0, 0, 0) => {
            let ms_mcs = ms as f64 + mcs as f64 / 1000.0;
            format!("{} ms", round_with_precision(ms_mcs, precision))
        }
        (d, h, m, s) => format_dhms(d, h, m, s),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_round_with_precision_0() {
        let round = |value| round_with_precision(value, 0);
        assert_eq!(0.0, round(0.0));
        assert_eq!(-0.0, round(-0.0));
        assert_eq!(0.0, round(0.4));
        assert_eq!(-0.0, round(-0.4));
        assert_eq!(1.0, round(0.56453));

Review comment (suggestion):
This function's name is quite long, and a bit misleading (precision of what? - only the documentation can clarify this). I think we can drop the `with_precision` part.

Code after:
    {
        return value;
    }
    let offset = 10_f64.powi(precision.into());
    assert!((value * offset).is_finite(), "{value} * {offset} is not finite!");
    (value * offset).round() / offset
}

/// Returns `(days, hours, minutes, seconds, milliseconds, microseconds)`.
fn get_duration_parts(duration: &Duration) -> (u64, u8, u8, u8, u16, u16) {
    // In practice we probably don't need nanoseconds.
    let micros = duration.as_micros();
    let (millis, micros) = (micros / 1000, (micros % 1000) as u16);
    let (sec, millis) = (millis / 1000, (millis % 1000) as u16);
    let (mins, sec) = (sec / 60, (sec % 60) as u8);
    let (hours, mins) = (mins / 60, (mins % 60) as u8);
    let (days, hours) = ((hours / 24) as u64, (hours % 24) as u8);
    (days, hours, mins, sec, millis, micros)
}

/// Format string using `days`, `hours`, `minutes`, `seconds`.
fn format_dhms(days: u64, hours: u8, minutes: u8, seconds: u8) -> String {
    match (days, hours, minutes, seconds) {
        (0, 0, 0, s) => format!("{s:2} s"),
        (0, 0, m, s) => format!("{m:2} m {s:2} s"),
        (0, h, m, s) => format!("{h:2} h {m:2} m {s:2} s"),
        (d, h, m, s) => format!("{d:3} d {h:2} h {m:2} m {s:2} s"),
    }
}

/// Format string starting with number of seconds and going bigger from there.
///
/// # Examples
///
/// ```
/// # use std::time::Duration;
/// # use typst_utils::format::time_starting_with_seconds;
/// let duration1 = time_starting_with_seconds(&Duration::from_secs(0));
/// assert_eq!(" 0 s", &duration1);
///
/// let duration2 = time_starting_with_seconds(&Duration::from_secs(
///     24 * 60 * 60 * 100 + // days
///     60 * 60 * 10 + // hours
///     60 * 10 + // minutes
///     10 // seconds
/// ));
/// assert_eq!("100 d 10 h 10 m 10 s", &duration2);
/// ```
pub fn time_starting_with_seconds(duration: &Duration) -> String {
    let (days, hours, minutes, seconds, _, _) = get_duration_parts(duration);
    format_dhms(days, hours, minutes, seconds)