livesplit_core/timing/formatter/
regular.rs

1use super::{
2    format_padded, Accuracy, TimeFormatter, DASH, MINUS, SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
3};
4use crate::TimeSpan;
5use core::fmt::{Display, Formatter, Result};
6
7pub struct Inner {
8    time: Option<TimeSpan>,
9    accuracy: Accuracy,
10}
11
12/// The Regular Time Formatter formats a [`TimeSpan`] to always show the minutes and
13/// is configurable by how many digits of the fractional part are shown. By
14/// default no fractional part is shown. This Time Formatter is most suitable
15/// for visualizing split times.
16///
17/// # Example Formatting
18///
19/// * Empty Time `—`
20/// * Seconds `0:23`
21/// * Minutes `12:34`
22/// * Hours `12:34:56`
23/// * Seconds with Hundredths `0:23.12`
24/// * Minutes with Hundredths `12:34.98`
25/// * Hours with Hundredths `12:34:56.12`
26/// * Negative Times `−0:23`
27pub struct Regular {
28    accuracy: Accuracy,
29}
30
31impl Regular {
32    /// Creates a new default Regular Time Formatter that doesn't show a
33    /// fractional part.
34    pub const fn new() -> Self {
35        Regular {
36            accuracy: Accuracy::Seconds,
37        }
38    }
39
40    /// Creates a new custom Regular Time Formatter where you can specify how
41    /// many digits to show for the fractional part.
42    pub const fn with_accuracy(accuracy: Accuracy) -> Self {
43        Regular { accuracy }
44    }
45}
46
47impl Default for Regular {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl TimeFormatter<'_> for Regular {
54    type Inner = Inner;
55
56    fn format<T>(&self, time: T) -> Self::Inner
57    where
58        T: Into<Option<TimeSpan>>,
59    {
60        Inner {
61            time: time.into(),
62            accuracy: self.accuracy,
63        }
64    }
65}
66
67impl Display for Inner {
68    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
69        if let Some(time) = self.time {
70            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
71            let (total_seconds, nanoseconds) = if (total_seconds | nanoseconds as i64) < 0 {
72                f.write_str(MINUS)?;
73                ((-total_seconds) as u64, (-nanoseconds) as u32)
74            } else {
75                (total_seconds as u64, nanoseconds as u32)
76            };
77            // These are intentionally not data dependent, such that the CPU can
78            // calculate all of them in parallel. On top of that they are
79            // integer divisions of known constants, which get turned into
80            // multiplies and shifts, which is very fast.
81            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
82            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
83            let hours = total_seconds / SECONDS_PER_HOUR;
84
85            let mut buffer = itoa::Buffer::new();
86
87            if hours > 0 {
88                f.write_str(buffer.format(hours))?;
89                f.write_str(":")?;
90                f.write_str(format_padded(minutes))?;
91            } else {
92                f.write_str(buffer.format(minutes))?;
93            }
94            f.write_str(":")?;
95            f.write_str(format_padded(seconds))?;
96            self.accuracy.format_nanoseconds(nanoseconds).fmt(f)
97        } else {
98            f.write_str(DASH)
99        }
100    }
101}