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}