livesplit_core/timing/formatter/
timer.rs

1//! The timing module provides a pair of Time Formatters that splits up the
2//! visualized time into the main part of the time and the fractional part. This
3//! is the Time Formatter pair used by the Timer Component.
4
5use super::{
6    format_padded, format_unpadded, Accuracy, DigitsFormat, TimeFormatter, DASH, MINUS,
7    SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
8};
9use crate::TimeSpan;
10use core::fmt::{Display, Formatter, Result};
11
12/// A Time Span to be formatted as the main part of the Time Formatter Pair.
13pub struct TimeInner {
14    time: Option<TimeSpan>,
15    digits_format: DigitsFormat,
16}
17
18/// The Time Formatter that visualizes the main part of the Time Formatter Pair
19/// for the Timer Component. This Time Formatter shows no fractional part and
20/// prefixes as many zeros as you want. By default no zeros are used as a prefix.
21///
22/// # Example Formatting
23///
24/// * Empty Time `—`
25/// * Seconds `23`
26/// * Minutes `12:34`
27/// * Hours `12:34:56`
28/// * Negative Times `−23`
29pub struct Time {
30    digits_format: DigitsFormat,
31}
32
33impl Time {
34    /// Creates a new default Time Formatter that doesn't prefix any zeros.
35    pub const fn new() -> Self {
36        Time {
37            digits_format: DigitsFormat::SingleDigitSeconds,
38        }
39    }
40
41    /// Creates a new Time Formatter that uses the digits format specified to
42    /// determine how many digits to always show. Zeros are prefixed to fill up
43    /// the missing digits.
44    pub const fn with_digits_format(digits_format: DigitsFormat) -> Self {
45        Time { digits_format }
46    }
47}
48
49impl Default for Time {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl TimeFormatter<'_> for Time {
56    type Inner = TimeInner;
57
58    fn format<T>(&self, time: T) -> Self::Inner
59    where
60        T: Into<Option<TimeSpan>>,
61    {
62        TimeInner {
63            time: time.into(),
64            digits_format: self.digits_format,
65        }
66    }
67}
68
69impl Display for TimeInner {
70    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
71        if let Some(time) = self.time {
72            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
73            let total_seconds = if (total_seconds | nanoseconds as i64) < 0 {
74                f.write_str(MINUS)?;
75                (-total_seconds) as u64
76            } else {
77                total_seconds as u64
78            };
79            // These are intentionally not data dependent, such that the CPU can
80            // calculate all of them in parallel. On top of that they are
81            // integer divisions of known constants, which get turned into
82            // multiplies and shifts, which is very fast.
83            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
84            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
85            let hours = total_seconds / SECONDS_PER_HOUR;
86
87            if self.digits_format == DigitsFormat::DoubleDigitHours {
88                let mut buffer = itoa::Buffer::new();
89                let hours = buffer.format(hours);
90                if hours.len() < 2 {
91                    f.write_str("0")?;
92                }
93                f.write_str(hours)?;
94                f.write_str(":")?;
95                f.write_str(format_padded(minutes))?;
96                f.write_str(":")?;
97                f.write_str(format_padded(seconds))
98            } else if hours > 0 || self.digits_format == DigitsFormat::SingleDigitHours {
99                f.write_str(itoa::Buffer::new().format(hours))?;
100                f.write_str(":")?;
101                f.write_str(format_padded(minutes))?;
102                f.write_str(":")?;
103                f.write_str(format_padded(seconds))
104            } else if self.digits_format == DigitsFormat::DoubleDigitMinutes {
105                f.write_str(format_padded(minutes))?;
106                f.write_str(":")?;
107                f.write_str(format_padded(seconds))
108            } else if minutes > 0 || self.digits_format == DigitsFormat::SingleDigitMinutes {
109                f.write_str(format_unpadded(minutes))?;
110                f.write_str(":")?;
111                f.write_str(format_padded(seconds))
112            } else if self.digits_format == DigitsFormat::DoubleDigitSeconds {
113                f.write_str(format_padded(seconds))
114            } else {
115                f.write_str(format_unpadded(seconds))
116            }
117        } else {
118            f.write_str(DASH)
119        }
120    }
121}
122
123/// A Time Span to be formatted as the fractional part of the Time Formatter
124/// Pair.
125pub struct FractionInner {
126    time: Option<TimeSpan>,
127    accuracy: Accuracy,
128}
129
130/// The Time Formatter that visualizes the fractional part of the Time Formatter
131/// Pair for the Timer Component. This Time Formatter shows only the fractional
132/// part of the time and uses as many digits for it as you want. By default 2
133/// digits of the fractional part are shown.
134///
135/// # Example Formatting
136///
137/// * Empty Time ``
138/// * No Fractional Part `​`
139/// * Tenths `.1`
140/// * Hundredths `.12`
141pub struct Fraction {
142    accuracy: Accuracy,
143}
144
145impl Fraction {
146    /// Creates a new default Time Formatter that uses hundredths for showing
147    /// the fractional part.
148    pub const fn new() -> Self {
149        Fraction {
150            accuracy: Accuracy::Hundredths,
151        }
152    }
153
154    /// Creates a new Time Formatter that uses the accuracy provided for showing
155    /// the fractional part.
156    pub const fn with_accuracy(accuracy: Accuracy) -> Self {
157        Fraction { accuracy }
158    }
159}
160
161impl Default for Fraction {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl TimeFormatter<'_> for Fraction {
168    type Inner = FractionInner;
169
170    fn format<T>(&self, time: T) -> Self::Inner
171    where
172        T: Into<Option<TimeSpan>>,
173    {
174        FractionInner {
175            time: time.into(),
176            accuracy: self.accuracy,
177        }
178    }
179}
180
181impl Display for FractionInner {
182    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
183        if let Some(time) = self.time {
184            let nanoseconds = time.to_duration().subsec_nanoseconds().unsigned_abs();
185            self.accuracy.format_nanoseconds(nanoseconds).fmt(f)
186        } else {
187            Ok(())
188        }
189    }
190}
191
192#[test]
193fn test() {
194    let time = "4:20.999999".parse::<TimeSpan>().unwrap();
195    assert_eq!(Fraction::new().format(Some(time)).to_string(), ".99");
196}