Skip to main content

qubit_clock/meter/
format.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! Duration and speed formatting utilities.
10//!
11//! This module provides functions to format durations and speeds into
12//! human-readable strings.
13//!
14//! # Author
15//!
16//! Haixing Hu
17
18/// Formats a duration in milliseconds into a human-readable string.
19///
20/// The format adapts based on the duration:
21/// - Less than 1 second: "X ms"
22/// - Less than 1 minute: "X.Ys" (rounded to 1 decimal place)
23/// - Less than 1 hour: "Xm Ys" (rounded to the nearest second)
24/// - 1 hour or more: "Xh Ym Zs" (rounded to the nearest second)
25///
26/// # Arguments
27///
28/// * `millis` - The duration in milliseconds
29///
30/// # Returns
31///
32/// A human-readable string representation of the duration
33///
34/// # Examples
35///
36/// ```
37/// use qubit_clock::meter::format_duration_millis;
38///
39/// assert_eq!(format_duration_millis(500), "500 ms");
40/// assert_eq!(format_duration_millis(1500), "1.5s");
41/// assert_eq!(format_duration_millis(65000), "1m 5s");
42/// assert_eq!(format_duration_millis(3665000), "1h 1m 5s");
43/// ```
44pub fn format_duration_millis(millis: i64) -> String {
45    format_duration_millis_i128(i128::from(millis))
46}
47
48/// Formats a value stored in tenths using the specified unit suffix.
49fn format_tenths(value_tenths: i128, unit: &str) -> String {
50    let whole = value_tenths / 10;
51    let tenths = value_tenths % 10;
52    if tenths > 0 {
53        format!("{}.{}{}", whole, tenths, unit)
54    } else {
55        format!("{}{}", whole, unit)
56    }
57}
58
59/// Divides a non-negative value by a positive divisor using half-up rounding.
60fn div_round_half_up(value: i128, divisor: i128) -> i128 {
61    let quotient = value / divisor;
62    let remainder = value % divisor;
63    if remainder >= divisor - remainder {
64        quotient.saturating_add(1)
65    } else {
66        quotient
67    }
68}
69
70/// Formats a non-negative duration expressed as whole seconds.
71fn format_duration_seconds(total_seconds: i128) -> String {
72    let hours = total_seconds / 3600;
73    let minutes = (total_seconds % 3600) / 60;
74    let seconds = total_seconds % 60;
75
76    if hours > 0 {
77        if minutes > 0 && seconds > 0 {
78            format!("{}h {}m {}s", hours, minutes, seconds)
79        } else if minutes > 0 {
80            format!("{}h {}m", hours, minutes)
81        } else if seconds > 0 {
82            format!("{}h {}s", hours, seconds)
83        } else {
84            format!("{}h", hours)
85        }
86    } else if minutes > 0 {
87        if seconds > 0 {
88            format!("{}m {}s", minutes, seconds)
89        } else {
90            format!("{}m", minutes)
91        }
92    } else {
93        format!("{}s", seconds)
94    }
95}
96
97/// Formats a millisecond duration stored as `i128`.
98///
99/// This helper keeps [`format_duration_nanos`] from truncating very large
100/// nanosecond values before formatting them.
101fn format_duration_millis_i128(millis: i128) -> String {
102    if millis < 0 {
103        return "0 ms".to_string();
104    }
105
106    if millis < 1000 {
107        return format!("{} ms", millis);
108    }
109
110    if millis < 60_000 {
111        let tenths = div_round_half_up(millis, 100);
112        if tenths >= 600 {
113            return format_duration_seconds(tenths / 10);
114        }
115        return format_tenths(tenths, "s");
116    }
117
118    format_duration_seconds(div_round_half_up(millis, 1000))
119}
120
121/// Formats a duration in nanoseconds into a human-readable string.
122///
123/// The format adapts based on the duration:
124/// - Less than 1 microsecond: "X ns"
125/// - Less than 1 millisecond: "X.Y μs" (rounded to 1 decimal place)
126/// - Less than 1 second: "X.Y ms" (rounded to 1 decimal place)
127/// - Less than 1 minute: "X.Ys" (rounded to 1 decimal place)
128/// - 1 minute or more: "Xm Ys" or "Xh Ym Zs" (rounded to the nearest second)
129///
130/// # Arguments
131///
132/// * `nanos` - The duration in nanoseconds
133///
134/// # Returns
135///
136/// A human-readable string representation of the duration
137///
138/// # Examples
139///
140/// ```
141/// use qubit_clock::meter::format_duration_nanos;
142///
143/// assert_eq!(format_duration_nanos(500), "500 ns");
144/// assert_eq!(format_duration_nanos(1500), "1.5 μs");
145/// assert_eq!(format_duration_nanos(1500000), "1.5 ms");
146/// assert_eq!(format_duration_nanos(1500000000), "1.5s");
147/// ```
148pub fn format_duration_nanos(nanos: i128) -> String {
149    if nanos < 0 {
150        return "0 ns".to_string();
151    }
152
153    if nanos < 1000 {
154        return format!("{} ns", nanos);
155    }
156
157    if nanos < 1_000_000 {
158        let tenths = div_round_half_up(nanos, 100);
159        if tenths >= 10_000 {
160            return format_duration_nanos(tenths * 100);
161        }
162        format_tenths(tenths, " μs")
163    } else if nanos < 1_000_000_000 {
164        let tenths = div_round_half_up(nanos, 100_000);
165        if tenths >= 10_000 {
166            return format_duration_nanos(tenths * 100_000);
167        }
168        format_tenths(tenths, " ms")
169    } else if nanos < 60_000_000_000 {
170        let tenths = div_round_half_up(nanos, 100_000_000);
171        if tenths >= 600 {
172            return format_duration_seconds(tenths / 10);
173        }
174        format_tenths(tenths, "s")
175    } else {
176        format_duration_seconds(div_round_half_up(nanos, 1_000_000_000))
177    }
178}
179
180/// Formats a speed value with a unit suffix.
181///
182/// The speed is formatted with 2 decimal places. If the speed is NaN or
183/// infinite, returns "N/A".
184///
185/// # Arguments
186///
187/// * `speed` - The speed value
188/// * `unit` - The unit suffix (e.g., "/s", "/m")
189///
190/// # Returns
191///
192/// A formatted string like "123.45/s" or "N/A"
193///
194/// # Examples
195///
196/// ```
197/// use qubit_clock::meter::format_speed;
198///
199/// assert_eq!(format_speed(123.456, "/s"), "123.46/s");
200/// assert_eq!(format_speed(0.0, "/m"), "0.00/m");
201/// assert_eq!(format_speed(f64::NAN, "/s"), "N/A");
202/// ```
203pub fn format_speed(speed: f64, unit: &str) -> String {
204    if speed.is_nan() || speed.is_infinite() {
205        "N/A".to_string()
206    } else {
207        format!("{:.2}{}", speed, unit)
208    }
209}