time_pretty/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::sync::LazyLock;
6
7// This may be already handled by something, but I don't want new deps.
8const USEC_IN_NANOS: i64 = 1000;
9pub const MSEC_IN_NANOS: i64 = 1000 * USEC_IN_NANOS;
10const SEC_IN_NANOS: i64 = 1000 * MSEC_IN_NANOS;
11const MIN_IN_NANOS: i64 = SEC_IN_NANOS * 60;
12const HOUR_IN_NANOS: i64 = MIN_IN_NANOS * 60;
13const DAY_IN_NANOS: i64 = HOUR_IN_NANOS * 24;
14const WEEK_IN_NANOS: i64 = DAY_IN_NANOS * 7;
15const YEAR_IN_NANOS: i64 = DAY_IN_NANOS * 365; // Approximate.
16
17static UNITS: LazyLock<Vec<(i64, &'static str)>> = LazyLock::new(|| {
18    vec![
19        (YEAR_IN_NANOS, "year(s)"),
20        (WEEK_IN_NANOS, "week(s)"),
21        (DAY_IN_NANOS, "day(s)"),
22        (HOUR_IN_NANOS, "h"),
23        (MIN_IN_NANOS, "min"),
24        (SEC_IN_NANOS, "s"),
25        (MSEC_IN_NANOS, "ms"),
26        (USEC_IN_NANOS, "μs"),
27        (1, "ns"),
28    ]
29});
30
31// Formats a time value into a simplistic human-readable string.  This is meant
32// to be a human-friendly, but not an impeccable format.
33pub fn format_common(mut value: i64) -> String {
34    let value_copy = value;
35    let mut repr: Vec<String> = vec![];
36    for (unit_value, unit_str) in UNITS.iter() {
37        if value == 0 {
38            break;
39        }
40        let num_units = value / unit_value;
41        if num_units.abs() > 0 {
42            repr.push(format!("{}{}", num_units, unit_str));
43            value = value % unit_value;
44        }
45    }
46    if repr.len() == 0 {
47        repr.push("0ns".to_string());
48    }
49    // 1year(s)_3week(s)_4day(s)_1h_2m_340ms. Not ideal but user-friendly enough.
50    let repr = repr.join("_");
51
52    let mut ret = vec![];
53    ret.push(repr);
54    // Also add the full nanosecond value too.
55    ret.push(format!("({})", value_copy));
56    ret.join(" ")
57}
58
59// Pretty prints a timer value into a simplistic format.
60pub fn format_timer<T: zx::Timeline>(timer: zx::Instant<T>) -> String {
61    format_common(timer.into_nanos())
62}
63
64// Pretty prints a duration into a simplistic format.
65pub fn format_duration<T: zx::Timeline>(duration: zx::Duration<T>) -> String {
66    format_common(duration.into_nanos())
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use test_case::test_case;
73
74    // Human readable duration formatting is useful.
75    #[test_case(0, "0ns (0)" ; "zero")]
76    #[test_case(1000, "1μs (1000)" ; "1us positive")]
77    #[test_case(-1000, "-1μs (-1000)"; "1us negative")]
78    #[test_case(YEAR_IN_NANOS, "1year(s) (31536000000000000)"; "A year")]
79    #[test_case(YEAR_IN_NANOS + 8 * DAY_IN_NANOS + 1,
80        "1year(s)_1week(s)_1day(s)_1ns (32227200000000001)" ; "A weird duration")]
81    #[test_case(2 * HOUR_IN_NANOS + 8 * MIN_IN_NANOS + 32 * SEC_IN_NANOS + 1,
82        "2h_8min_32s_1ns (7712000000001)" ; "A reasonable long duration")]
83    fn test_format_common(value: i64, repr: &str) {
84        assert_eq!(format_common(value), repr.to_string());
85    }
86}