livesplit_core/timing/formatter/
delta.rs

1use super::{
2    format_padded, Accuracy, TimeFormatter, DASH, MINUS, PLUS, 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    drop_decimals: bool,
10    accuracy: Accuracy,
11}
12
13/// The Delta Time Formatter formats a [`TimeSpan`] as a comparison of two
14/// durations, so that it visualizes the difference between both of them.
15/// Therefore it always shows whether it is a positive or negative difference,
16/// by prepending a plus or minus sign. You can choose how many digits of the
17/// fractional part are visualized. Additionally there's an option for removing
18/// the fractional part for deltas that are larger than 1 minute.
19///
20/// # Example Formatting
21///
22/// * Empty Time `—`
23/// * Seconds `+23.1`
24/// * Minutes without Decimal Dropping `+12:34.9`
25/// * Minutes with Decimal Dropping `+12:34`
26/// * Hours without Decimal Dropping `+12:34:56.1`
27/// * Hours with Decimal Dropping `+12:34:56`
28/// * Negative Times `−23.1`
29pub struct Delta(bool, Accuracy);
30
31impl Delta {
32    /// Creates a new default Delta Time Formatter that drops the fractional
33    /// part and uses tenths when showing the fractional part.
34    pub const fn new() -> Self {
35        Delta(true, Accuracy::Tenths)
36    }
37
38    /// Creates a new custom Delta Time Formatter where you can specify whether
39    /// the fractional part should be dropped for deltas that are larger than 1
40    /// minute and how many digits to show for the fractional part.
41    pub const fn custom(drop_decimals: bool, accuracy: Accuracy) -> Self {
42        Delta(drop_decimals, accuracy)
43    }
44
45    /// Creates a new Delta Time Formatter that drops the fractional part and
46    /// uses tenths when showing the fractional part.
47    pub const fn with_decimal_dropping() -> Self {
48        Delta(true, Accuracy::Tenths)
49    }
50}
51
52impl Default for Delta {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl TimeFormatter<'_> for Delta {
59    type Inner = Inner;
60
61    fn format<T>(&self, time: T) -> Self::Inner
62    where
63        T: Into<Option<TimeSpan>>,
64    {
65        Inner {
66            time: time.into(),
67            drop_decimals: self.0,
68            accuracy: self.1,
69        }
70    }
71}
72
73impl Display for Inner {
74    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
75        if let Some(time) = self.time {
76            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
77            let (total_seconds, nanoseconds) = if (total_seconds | nanoseconds as i64) < 0 {
78                f.write_str(MINUS)?;
79                ((-total_seconds) as u64, (-nanoseconds) as u32)
80            } else {
81                f.write_str(PLUS)?;
82                (total_seconds as u64, nanoseconds as u32)
83            };
84            // These are intentionally not data dependent, such that the CPU can
85            // calculate all of them in parallel. On top of that they are
86            // integer divisions of known constants, which get turned into
87            // multiplies and shifts, which is very fast.
88            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
89            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
90            let hours = total_seconds / SECONDS_PER_HOUR;
91
92            let mut buffer = itoa::Buffer::new();
93
94            if hours > 0 {
95                f.write_str(buffer.format(hours))?;
96                f.write_str(":")?;
97                f.write_str(format_padded(minutes))?;
98                f.write_str(":")?;
99                f.write_str(format_padded(seconds))?;
100            } else if minutes > 0 {
101                f.write_str(buffer.format(minutes))?;
102                f.write_str(":")?;
103                f.write_str(format_padded(seconds))?;
104            } else {
105                f.write_str(buffer.format(seconds))?;
106                return self.accuracy.format_nanoseconds(nanoseconds).fmt(f);
107            }
108            if !self.drop_decimals {
109                self.accuracy.format_nanoseconds(nanoseconds).fmt(f)
110            } else {
111                Ok(())
112            }
113        } else {
114            f.write_str(DASH)
115        }
116    }
117}