apollo_framework/
fmt.rs

1//! Provides formatting helpers for durations and byte sizes.
2use std::fmt::Write;
3use std::time::Duration;
4
5/// Formats a duration given in microseconds.
6///
7/// This function determines the ideal unit (ranging from microseconds to seconds) to provide
8/// a concise representation.
9///
10/// Note that a helper function [format_short_duration](format_short_duration) is also provided
11/// which directly returns a String. This function also provides some examples.
12pub fn format_micros(micros: i32, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
13    if micros < 1_000 {
14        write!(f, "{} us", micros)
15    } else if micros < 10_000 {
16        write!(f, "{:.2} ms", micros as f32 / 1_000.)
17    } else if micros < 100_000 {
18        write!(f, "{:.1} ms", micros as f32 / 1_000.)
19    } else if micros < 1_000_000 {
20        write!(f, "{} ms", micros / 1_000)
21    } else if micros < 10_000_000 {
22        write!(f, "{:.2} s", micros as f32 / 1_000_000.)
23    } else if micros < 100_000_000 {
24        write!(f, "{:.1} s", micros as f32 / 1_000_000.)
25    } else {
26        write!(f, "{} s", micros / 1_000_000)
27    }
28}
29
30/// Formats a duration given in microseconds and returns a String representation.
31///
32/// This function determines the ideal unit (ranging from microseconds to seconds) to provide
33/// a concise representation.
34///
35/// Note that a helper function [format_micros](format_micros) is also provided
36/// which directly consumes a **std::fmt::Write**.
37///
38/// # Examples
39///
40/// ```
41/// assert_eq!(apollo_framework::fmt::format_short_duration(100), "100 us");
42/// assert_eq!(apollo_framework::fmt::format_short_duration(8_192), "8.19 ms");
43/// assert_eq!(apollo_framework::fmt::format_short_duration(32_768), "32.8 ms");
44/// assert_eq!(apollo_framework::fmt::format_short_duration(128_123), "128 ms");
45/// assert_eq!(apollo_framework::fmt::format_short_duration(1_128_123), "1.13 s");
46/// assert_eq!(apollo_framework::fmt::format_short_duration(10_128_123), "10.1 s");
47/// assert_eq!(apollo_framework::fmt::format_short_duration(101_000_000), "101 s");
48/// ```
49pub fn format_short_duration(duration_in_micros: i32) -> String {
50    let mut result = String::new();
51    let _ = format_micros(duration_in_micros, &mut result);
52    result
53}
54
55/// Formats a given size in bytes.
56///
57/// This function determines the ideal unit (ranging from bytes to petabytes) to provide
58/// a concise representation.
59///
60/// Note that a helper function [format_size](format_size) is also provided
61/// which directly returns a String. This function also provides some examples.
62pub fn format_bytes(size_in_bytes: usize, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
63    if size_in_bytes == 1 {
64        return write!(f, "1 byte");
65    } else if size_in_bytes < 1024 {
66        return write!(f, "{} bytes", size_in_bytes);
67    }
68
69    let mut magnitude = 0;
70    let mut size = size_in_bytes as f32;
71    while size > 1024. && magnitude < 5 {
72        size /= 1024.;
73        magnitude += 1;
74    }
75
76    if size <= 10. {
77        write!(f, "{:.2} ", size)?;
78    } else if size <= 100. {
79        write!(f, "{:.1} ", size)?;
80    } else {
81        write!(f, "{:.0} ", size)?;
82    }
83
84    match magnitude {
85        0 => write!(f, "Bytes"),
86        1 => write!(f, "KiB"),
87        2 => write!(f, "MiB"),
88        3 => write!(f, "GiB"),
89        4 => write!(f, "TiB"),
90        _ => write!(f, "PiB"),
91    }
92}
93
94/// Formats a given size in bytes.
95///
96/// This function determines the ideal unit (ranging from bytes to petabytes) to provide
97/// a concise representation.
98///
99/// Note that a helper function [format_bytes](format_bytes) is also provided
100/// which directly consumes a **std::fmt::Write**.
101///
102/// # Examples
103///
104/// ```
105/// assert_eq!(apollo_framework::fmt::format_size(0), "0 bytes");
106/// assert_eq!(apollo_framework::fmt::format_size(1), "1 byte");
107/// assert_eq!(apollo_framework::fmt::format_size(100), "100 bytes");
108/// assert_eq!(apollo_framework::fmt::format_size(8_734), "8.53 KiB");
109/// assert_eq!(apollo_framework::fmt::format_size(87_340), "85.3 KiB");
110/// assert_eq!(apollo_framework::fmt::format_size(873_400), "853 KiB");
111/// assert_eq!(apollo_framework::fmt::format_size(8_734_000), "8.33 MiB");
112/// assert_eq!(apollo_framework::fmt::format_size(87_340_000), "83.3 MiB");
113/// assert_eq!(apollo_framework::fmt::format_size(873_400_000), "833 MiB");
114/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000), "8.13 GiB");
115/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000), "81.3 GiB");
116/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000), "813 GiB");
117/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000_000), "7.94 TiB");
118/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000_000), "79.4 TiB");
119/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000_000), "794 TiB");
120/// assert_eq!(apollo_framework::fmt::format_size(8_734_000_000_000_000), "7.76 PiB");
121/// assert_eq!(apollo_framework::fmt::format_size(87_340_000_000_000_000), "77.6 PiB");
122/// assert_eq!(apollo_framework::fmt::format_size(873_400_000_000_000_000), "776 PiB");
123/// ```
124pub fn format_size(size_in_bytes: usize) -> String {
125    let mut result = String::new();
126    let _ = format_bytes(size_in_bytes, &mut result);
127
128    result
129}
130
131/// Parses a file size from a given string.
132///
133/// This string can have the following suffixes:
134/// * **k** or **K**: multiplies the given value by 1024 thus treats the value as KiB
135/// * **m** or **M**: multiplies the given value by 1.048.576 thus treats the value as MiB
136/// * **g** or **G**: multiplies the given value by 1.073.741.824 thus treats the value as GiB
137/// * **t** or **T**: multiplies the given value by 1.099.511.627.776 thus treats the value as TiB
138///
139/// Returns an **Err** if either a non-integer value is given or if an unknow suffix was provided.
140///
141/// # Examples
142///
143/// ```
144/// assert_eq!(apollo_framework::fmt::parse_size("100").unwrap(), 100);
145/// assert_eq!(apollo_framework::fmt::parse_size("100b").unwrap(), 100);
146/// assert_eq!(apollo_framework::fmt::parse_size("8k").unwrap(), 8192);
147/// assert_eq!(apollo_framework::fmt::parse_size("8m").unwrap(), 8 * 1024 * 1024);
148/// assert_eq!(apollo_framework::fmt::parse_size("4 G").unwrap(), 4 * 1024 * 1024 * 1024);
149/// assert_eq!(apollo_framework::fmt::parse_size("3 T").unwrap(), 3 * 1024 * 1024 * 1024 * 1024);
150///
151/// // An invalid suffix results in an error...
152/// assert_eq!(apollo_framework::fmt::parse_size("3 Y").is_err(), true);
153///
154/// // Decimal numbers result in an error...
155/// assert_eq!(apollo_framework::fmt::parse_size("1.2g").is_err(), true);
156///
157/// // Negative numbers result in an error...
158/// assert_eq!(apollo_framework::fmt::parse_size("-1").is_err(), true);
159/// ```
160pub fn parse_size(str: impl AsRef<str>) -> anyhow::Result<usize> {
161    lazy_static::lazy_static! {
162        static ref NUMBER_AND_SUFFIX: regex::Regex =
163            regex::Regex::new(r"^ *(\d+) *([bBkKmMgGtT]?) *$").unwrap();
164    }
165
166    match NUMBER_AND_SUFFIX.captures(str.as_ref()) {
167        Some(captures) => {
168            let number = captures[1].parse::<usize>().unwrap();
169            match &captures[2] {
170                "k" | "K" => Ok(number * 1024),
171                "m" | "M" => Ok(number * 1024 * 1024),
172                "g" | "G" => Ok(number * 1024 * 1024 * 1024),
173                "t" | "T" => Ok(number * 1024 * 1024 * 1024 * 1024),
174                _ => Ok(number),
175            }
176        }
177        None => Err(anyhow::anyhow!(
178            "Cannot parse '{}' into a size expression.\
179             Expected a positive number and optionally 'b', 'k', 'm', 'g' or 't' as suffix.",
180            str.as_ref()
181        )),
182    }
183}
184
185/// Parses a duration from a given string.
186///
187/// This string can have the following suffixes:
188/// * **ms** or **MS**: treats the value as milliseconds
189/// * **s** or **S**: treats the value as seconds
190/// * **m** or **M**: treats the value as minutes
191/// * **h** or **H**: treats the value as hours
192/// * **d** or **D**: treats the value as days
193///
194/// Returns an **Err** if either a non-integer value is given or if an unknow suffix was provided.
195///
196/// # Examples
197///
198/// ```
199/// # use std::time::Duration;
200/// assert_eq!(apollo_framework::fmt::parse_duration("100 ms").unwrap(), Duration::from_millis(100));
201/// assert_eq!(apollo_framework::fmt::parse_duration("12 s").unwrap(), Duration::from_secs(12));
202/// assert_eq!(apollo_framework::fmt::parse_duration("3 M").unwrap(), Duration::from_secs(3 * 60));
203/// assert_eq!(apollo_framework::fmt::parse_duration("2 H").unwrap(), Duration::from_secs(2 * 60 * 60));
204/// assert_eq!(apollo_framework::fmt::parse_duration("5 d").unwrap(), Duration::from_secs(5 * 24 * 60 * 60));
205///
206/// // An invalid suffix results in an error...
207/// assert_eq!(apollo_framework::fmt::parse_duration("3 Y").is_err(), true);
208///
209/// // Decimal numbers result in an error...
210/// assert_eq!(apollo_framework::fmt::parse_duration("1.2s").is_err(), true);
211///
212/// // Negative numbers result in an error...
213/// assert_eq!(apollo_framework::fmt::parse_duration("-1m").is_err(), true);
214/// ```
215pub fn parse_duration(str: impl AsRef<str>) -> anyhow::Result<Duration> {
216    lazy_static::lazy_static! {
217        static ref NUMBER_AND_SUFFIX: regex::Regex =
218            regex::Regex::new(r"^ *(\d+) *((ms|s|m|h|d|MS|S|M|H|D)?) *$").unwrap();
219    }
220
221    match NUMBER_AND_SUFFIX.captures(str.as_ref()) {
222        Some(captures) => {
223            let number = captures[1].parse::<u64>().unwrap();
224            match &captures[2] {
225                "s" | "S" => Ok(Duration::from_secs(number)),
226                "m" | "M" => Ok(Duration::from_secs(number * 60)),
227                "h" | "H" => Ok(Duration::from_secs(number * 60 * 60)),
228                "d" | "D" => Ok(Duration::from_secs(number * 60 * 60 * 24)),
229                _ => Ok(Duration::from_millis(number)),
230            }
231        }
232        None => Err(anyhow::anyhow!(
233            "Cannot parse '{}' into a duration expression.\
234             Expected a positive number an optionally 'ms', 's', 'm', 'h' or 'd' as suffix.",
235            str.as_ref()
236        )),
237    }
238}
239
240/// Formats a duration into a string like "5d 3h 17m 2s 12ms".
241///
242/// As the format indicates this is mostly used for "shorter" durations which rather run in
243/// seconds or minutes rather than several days. However, if required, we can still format such
244/// a value, even if outputting milliseconds in this case is kind of questionable.
245///
246/// Also note that for formatting sort durations (in microseconds) we have a dedicated function
247/// named [format_short_duration](format_short_duration).
248///
249/// # Examples
250///
251/// ```
252/// # use std::time::Duration;
253/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(13)), "13ms");
254/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(1013)), "1s 13ms");
255/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_millis(62_013)), "1m 2s 13ms");
256/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(60 * 32 + 13)), "32m 13s");
257/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(60 * 61)), "1h 1m");
258/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(4 * 60 * 60)), "4h");
259/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(24 * 60 * 60 + 60 * 60 + 60)), "1d 1h 1m");
260/// assert_eq!(apollo_framework::fmt::format_duration(Duration::from_secs(24 * 60 * 60 + 60 * 60 + 59)), "1d 1h 59s");
261/// ```
262pub fn format_duration(duration: Duration) -> String {
263    let mut result = String::new();
264
265    let mut value = duration.as_millis();
266    {
267        let days = value / (1000 * 60 * 60 * 24);
268        if days > 0 {
269            let _ = write!(result, "{}d", days);
270            value %= 1000 * 60 * 60 * 24;
271        }
272    }
273    {
274        let hours = value / (1000 * 60 * 60);
275        if hours > 0 {
276            if !result.is_empty() {
277                result.push(' ');
278            }
279            let _ = write!(result, "{}h", hours);
280            value %= 1000 * 60 * 60;
281        }
282    }
283    {
284        let minutes = value / (1000 * 60);
285        if minutes > 0 {
286            if !result.is_empty() {
287                result.push(' ');
288            }
289            let _ = write!(result, "{}m", minutes);
290            value %= 1000 * 60;
291        }
292    }
293    {
294        let seconds = value / 1000;
295        if seconds > 0 {
296            if !result.is_empty() {
297                result.push(' ');
298            }
299            let _ = write!(result, "{}s", seconds);
300            value %= 1000;
301        }
302    }
303    if value > 0 {
304        if !result.is_empty() {
305            result.push(' ');
306        }
307        let _ = write!(result, "{}ms", value);
308    }
309
310    result
311}