indicatif/
format.rs

1use std::fmt;
2use std::time::Duration;
3
4use unit_prefix::NumberPrefix;
5
6const SECOND: Duration = Duration::from_secs(1);
7const MINUTE: Duration = Duration::from_secs(60);
8const HOUR: Duration = Duration::from_secs(60 * 60);
9const DAY: Duration = Duration::from_secs(24 * 60 * 60);
10const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60);
11const YEAR: Duration = Duration::from_secs(365 * 24 * 60 * 60);
12
13/// Wraps an std duration for human basic formatting.
14#[derive(Debug)]
15pub struct FormattedDuration(pub Duration);
16
17/// Wraps an std duration for human readable formatting.
18#[derive(Debug)]
19pub struct HumanDuration(pub Duration);
20
21/// Formats bytes for human readability
22///
23/// # Examples
24/// ```rust
25/// # use indicatif::HumanBytes;
26/// assert_eq!("15 B",     format!("{}", HumanBytes(15)));
27/// assert_eq!("1.46 KiB", format!("{}", HumanBytes(1_500)));
28/// assert_eq!("1.43 MiB", format!("{}", HumanBytes(1_500_000)));
29/// assert_eq!("1.40 GiB", format!("{}", HumanBytes(1_500_000_000)));
30/// assert_eq!("1.36 TiB", format!("{}", HumanBytes(1_500_000_000_000)));
31/// assert_eq!("1.33 PiB", format!("{}", HumanBytes(1_500_000_000_000_000)));
32/// ```
33#[derive(Debug)]
34pub struct HumanBytes(pub u64);
35
36/// Formats bytes for human readability using SI prefixes
37///
38/// # Examples
39/// ```rust
40/// # use indicatif::DecimalBytes;
41/// assert_eq!("15 B",    format!("{}", DecimalBytes(15)));
42/// assert_eq!("1.50 kB", format!("{}", DecimalBytes(1_500)));
43/// assert_eq!("1.50 MB", format!("{}", DecimalBytes(1_500_000)));
44/// assert_eq!("1.50 GB", format!("{}", DecimalBytes(1_500_000_000)));
45/// assert_eq!("1.50 TB", format!("{}", DecimalBytes(1_500_000_000_000)));
46/// assert_eq!("1.50 PB", format!("{}", DecimalBytes(1_500_000_000_000_000)));
47/// ```
48#[derive(Debug)]
49pub struct DecimalBytes(pub u64);
50
51/// Formats bytes for human readability using ISO/IEC prefixes
52///
53/// # Examples
54/// ```rust
55/// # use indicatif::BinaryBytes;
56/// assert_eq!("15 B",     format!("{}", BinaryBytes(15)));
57/// assert_eq!("1.46 KiB", format!("{}", BinaryBytes(1_500)));
58/// assert_eq!("1.43 MiB", format!("{}", BinaryBytes(1_500_000)));
59/// assert_eq!("1.40 GiB", format!("{}", BinaryBytes(1_500_000_000)));
60/// assert_eq!("1.36 TiB", format!("{}", BinaryBytes(1_500_000_000_000)));
61/// assert_eq!("1.33 PiB", format!("{}", BinaryBytes(1_500_000_000_000_000)));
62/// ```
63#[derive(Debug)]
64pub struct BinaryBytes(pub u64);
65
66/// Formats counts for human readability using commas
67#[derive(Debug)]
68pub struct HumanCount(pub u64);
69
70/// Formats counts for human readability using commas for floats
71#[derive(Debug)]
72pub struct HumanFloatCount(pub f64);
73
74impl fmt::Display for FormattedDuration {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        let mut t = self.0.as_secs();
77        let seconds = t % 60;
78        t /= 60;
79        let minutes = t % 60;
80        t /= 60;
81        let hours = t % 24;
82        t /= 24;
83        if t > 0 {
84            let days = t;
85            write!(f, "{days}d {hours:02}:{minutes:02}:{seconds:02}")
86        } else {
87            write!(f, "{hours:02}:{minutes:02}:{seconds:02}")
88        }
89    }
90}
91
92// `HumanDuration` should be as intuitively understandable as possible.
93// So we want to round, not truncate: otherwise 1 hour and 59 minutes
94// would display an ETA of "1 hour" which underestimates the time
95// remaining by a factor 2.
96//
97// To make the precision more uniform, we avoid displaying "1 unit"
98// (except for seconds), because it would be displayed for a relatively
99// long duration compared to the unit itself. Instead, when we arrive
100// around 1.5 unit, we change from "2 units" to the next smaller unit
101// (e.g. "89 seconds").
102//
103// Formally:
104// * for n >= 2, we go from "n+1 units" to "n units" exactly at (n + 1/2) units
105// * we switch from "2 units" to the next smaller unit at (1.5 unit minus half of the next smaller unit)
106
107impl fmt::Display for HumanDuration {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        let mut idx = 0;
110        for (i, &(cur, _, _)) in UNITS.iter().enumerate() {
111            idx = i;
112            match UNITS.get(i + 1) {
113                Some(&next) if self.0.saturating_add(next.0 / 2) >= cur + cur / 2 => break,
114                _ => continue,
115            }
116        }
117
118        let (unit, name, alt) = UNITS[idx];
119        // FIXME when `div_duration_f64` is stable
120        let mut t = (self.0.as_secs_f64() / unit.as_secs_f64()).round() as usize;
121        if idx < UNITS.len() - 1 {
122            t = Ord::max(t, 2);
123        }
124
125        match (f.alternate(), t) {
126            (true, _) => write!(f, "{t}{alt}"),
127            (false, 1) => write!(f, "{t} {name}"),
128            (false, _) => write!(f, "{t} {name}s"),
129        }
130    }
131}
132
133const UNITS: &[(Duration, &str, &str)] = &[
134    (YEAR, "year", "y"),
135    (WEEK, "week", "w"),
136    (DAY, "day", "d"),
137    (HOUR, "hour", "h"),
138    (MINUTE, "minute", "m"),
139    (SECOND, "second", "s"),
140];
141
142impl fmt::Display for HumanBytes {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match NumberPrefix::binary(self.0 as f64) {
145            NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"),
146            NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"),
147        }
148    }
149}
150
151impl fmt::Display for DecimalBytes {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match NumberPrefix::decimal(self.0 as f64) {
154            NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"),
155            NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"),
156        }
157    }
158}
159
160impl fmt::Display for BinaryBytes {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match NumberPrefix::binary(self.0 as f64) {
163            NumberPrefix::Standalone(number) => write!(f, "{number:.0} B"),
164            NumberPrefix::Prefixed(prefix, number) => write!(f, "{number:.2} {prefix}B"),
165        }
166    }
167}
168
169impl fmt::Display for HumanCount {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        use fmt::Write;
172
173        let num = self.0.to_string();
174        let len = num.len();
175        for (idx, c) in num.chars().enumerate() {
176            let pos = len - idx - 1;
177            f.write_char(c)?;
178            if pos > 0 && pos % 3 == 0 {
179                f.write_char(',')?;
180            }
181        }
182        Ok(())
183    }
184}
185
186impl fmt::Display for HumanFloatCount {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        use fmt::Write;
189
190        // Use formatter's precision if provided, otherwise default to 4
191        let precision = f.precision().unwrap_or(4);
192        let num = format!("{:.*}", precision, self.0);
193
194        let (int_part, frac_part) = match num.split_once('.') {
195            Some((int_str, fract_str)) => (int_str.to_string(), fract_str),
196            None => (self.0.trunc().to_string(), ""),
197        };
198        let len = int_part.len();
199        for (idx, c) in int_part.chars().enumerate() {
200            let pos = len - idx - 1;
201            f.write_char(c)?;
202            if pos > 0 && pos % 3 == 0 {
203                f.write_char(',')?;
204            }
205        }
206        let frac_trimmed = frac_part.trim_end_matches('0');
207        if !frac_trimmed.is_empty() {
208            f.write_char('.')?;
209            f.write_str(frac_trimmed)?;
210        }
211        Ok(())
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    const MILLI: Duration = Duration::from_millis(1);
220
221    #[test]
222    fn human_duration_alternate() {
223        for (unit, _, alt) in UNITS {
224            assert_eq!(format!("2{alt}"), format!("{:#}", HumanDuration(2 * *unit)));
225        }
226    }
227
228    #[test]
229    fn human_duration_less_than_one_second() {
230        assert_eq!(
231            "0 seconds",
232            format!("{}", HumanDuration(Duration::from_secs(0)))
233        );
234        assert_eq!("0 seconds", format!("{}", HumanDuration(MILLI)));
235        assert_eq!("0 seconds", format!("{}", HumanDuration(499 * MILLI)));
236        assert_eq!("1 second", format!("{}", HumanDuration(500 * MILLI)));
237        assert_eq!("1 second", format!("{}", HumanDuration(999 * MILLI)));
238    }
239
240    #[test]
241    fn human_duration_less_than_two_seconds() {
242        assert_eq!("1 second", format!("{}", HumanDuration(1499 * MILLI)));
243        assert_eq!("2 seconds", format!("{}", HumanDuration(1500 * MILLI)));
244        assert_eq!("2 seconds", format!("{}", HumanDuration(1999 * MILLI)));
245    }
246
247    #[test]
248    fn human_duration_one_unit() {
249        assert_eq!("1 second", format!("{}", HumanDuration(SECOND)));
250        assert_eq!("60 seconds", format!("{}", HumanDuration(MINUTE)));
251        assert_eq!("60 minutes", format!("{}", HumanDuration(HOUR)));
252        assert_eq!("24 hours", format!("{}", HumanDuration(DAY)));
253        assert_eq!("7 days", format!("{}", HumanDuration(WEEK)));
254        assert_eq!("52 weeks", format!("{}", HumanDuration(YEAR)));
255    }
256
257    #[test]
258    fn human_duration_less_than_one_and_a_half_unit() {
259        // this one is actually done at 1.5 unit - half of the next smaller unit - epsilon
260        // and should display the next smaller unit
261        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2 - MILLI);
262        assert_eq!("89 seconds", format!("{d}"));
263        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2 - MILLI);
264        assert_eq!("89 minutes", format!("{d}"));
265        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2 - MILLI);
266        assert_eq!("35 hours", format!("{d}"));
267        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2 - MILLI);
268        assert_eq!("10 days", format!("{d}"));
269        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2 - MILLI);
270        assert_eq!("78 weeks", format!("{d}"));
271    }
272
273    #[test]
274    fn human_duration_one_and_a_half_unit() {
275        // this one is actually done at 1.5 unit - half of the next smaller unit
276        // and should still display "2 units"
277        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2);
278        assert_eq!("2 minutes", format!("{d}"));
279        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2);
280        assert_eq!("2 hours", format!("{d}"));
281        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2);
282        assert_eq!("2 days", format!("{d}"));
283        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2);
284        assert_eq!("2 weeks", format!("{d}"));
285        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2);
286        assert_eq!("2 years", format!("{d}"));
287    }
288
289    #[test]
290    fn human_duration_two_units() {
291        assert_eq!("2 seconds", format!("{}", HumanDuration(2 * SECOND)));
292        assert_eq!("2 minutes", format!("{}", HumanDuration(2 * MINUTE)));
293        assert_eq!("2 hours", format!("{}", HumanDuration(2 * HOUR)));
294        assert_eq!("2 days", format!("{}", HumanDuration(2 * DAY)));
295        assert_eq!("2 weeks", format!("{}", HumanDuration(2 * WEEK)));
296        assert_eq!("2 years", format!("{}", HumanDuration(2 * YEAR)));
297    }
298
299    #[test]
300    fn human_duration_less_than_two_and_a_half_units() {
301        let d = HumanDuration(2 * SECOND + SECOND / 2 - MILLI);
302        assert_eq!("2 seconds", format!("{d}"));
303        let d = HumanDuration(2 * MINUTE + MINUTE / 2 - MILLI);
304        assert_eq!("2 minutes", format!("{d}"));
305        let d = HumanDuration(2 * HOUR + HOUR / 2 - MILLI);
306        assert_eq!("2 hours", format!("{d}"));
307        let d = HumanDuration(2 * DAY + DAY / 2 - MILLI);
308        assert_eq!("2 days", format!("{d}"));
309        let d = HumanDuration(2 * WEEK + WEEK / 2 - MILLI);
310        assert_eq!("2 weeks", format!("{d}"));
311        let d = HumanDuration(2 * YEAR + YEAR / 2 - MILLI);
312        assert_eq!("2 years", format!("{d}"));
313    }
314
315    #[test]
316    fn human_duration_two_and_a_half_units() {
317        let d = HumanDuration(2 * SECOND + SECOND / 2);
318        assert_eq!("3 seconds", format!("{d}"));
319        let d = HumanDuration(2 * MINUTE + MINUTE / 2);
320        assert_eq!("3 minutes", format!("{d}"));
321        let d = HumanDuration(2 * HOUR + HOUR / 2);
322        assert_eq!("3 hours", format!("{d}"));
323        let d = HumanDuration(2 * DAY + DAY / 2);
324        assert_eq!("3 days", format!("{d}"));
325        let d = HumanDuration(2 * WEEK + WEEK / 2);
326        assert_eq!("3 weeks", format!("{d}"));
327        let d = HumanDuration(2 * YEAR + YEAR / 2);
328        assert_eq!("3 years", format!("{d}"));
329    }
330
331    #[test]
332    fn human_duration_three_units() {
333        assert_eq!("3 seconds", format!("{}", HumanDuration(3 * SECOND)));
334        assert_eq!("3 minutes", format!("{}", HumanDuration(3 * MINUTE)));
335        assert_eq!("3 hours", format!("{}", HumanDuration(3 * HOUR)));
336        assert_eq!("3 days", format!("{}", HumanDuration(3 * DAY)));
337        assert_eq!("3 weeks", format!("{}", HumanDuration(3 * WEEK)));
338        assert_eq!("3 years", format!("{}", HumanDuration(3 * YEAR)));
339    }
340
341    #[test]
342    fn human_count() {
343        assert_eq!("42", format!("{}", HumanCount(42)));
344        assert_eq!("7,654", format!("{}", HumanCount(7654)));
345        assert_eq!("12,345", format!("{}", HumanCount(12345)));
346        assert_eq!("1,234,567,890", format!("{}", HumanCount(1234567890)));
347    }
348
349    #[test]
350    fn human_float_count() {
351        assert_eq!("42", format!("{}", HumanFloatCount(42.0)));
352        assert_eq!("7,654", format!("{}", HumanFloatCount(7654.0)));
353        assert_eq!("12,345", format!("{}", HumanFloatCount(12345.0)));
354        assert_eq!(
355            "1,234,567,890",
356            format!("{}", HumanFloatCount(1234567890.0))
357        );
358        assert_eq!("42.5", format!("{}", HumanFloatCount(42.5)));
359        assert_eq!("42.5", format!("{}", HumanFloatCount(42.500012345)));
360        assert_eq!("42.502", format!("{}", HumanFloatCount(42.502012345)));
361        assert_eq!("7,654.321", format!("{}", HumanFloatCount(7654.321)));
362        assert_eq!("7,654.321", format!("{}", HumanFloatCount(7654.3210123456)));
363        assert_eq!("12,345.6789", format!("{}", HumanFloatCount(12345.6789)));
364        assert_eq!(
365            "1,234,567,890.1235",
366            format!("{}", HumanFloatCount(1234567890.1234567))
367        );
368        assert_eq!(
369            "1,234,567,890.1234",
370            format!("{}", HumanFloatCount(1234567890.1234321))
371        );
372        assert_eq!("1,234", format!("{:.0}", HumanFloatCount(1234.1234321)));
373        assert_eq!("1,234.1", format!("{:.1}", HumanFloatCount(1234.1234321)));
374        assert_eq!("1,234.12", format!("{:.2}", HumanFloatCount(1234.1234321)));
375        assert_eq!("1,234.123", format!("{:.3}", HumanFloatCount(1234.1234321)));
376        assert_eq!(
377            "1,234.1234320999999454215867445",
378            format!("{:.25}", HumanFloatCount(1234.1234321))
379        );
380    }
381}