livesplit_core/timing/formatter/
segment_time.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 Segment Time Formatter formats a [`TimeSpan`] for them to be shown as
13/// Segment Times. This specifically means that the fractional part of the time
14/// is always shown and the minutes and hours are only shown when necessary. The
15/// default accuracy is to show 2 digits of the fractional part, but this can be
16/// configured.
17///
18/// # Example Formatting
19///
20/// * Empty Time `—`
21/// * Seconds `23.12`
22/// * Minutes `12:34.98`
23/// * Hours `12:34:56.12`
24/// * Negative Times `−23.12`
25pub struct SegmentTime {
26    accuracy: Accuracy,
27}
28
29impl SegmentTime {
30    /// Creates a new Segment Time Formatter that uses hundredths for showing
31    /// the fractional part.
32    pub const fn new() -> Self {
33        SegmentTime {
34            accuracy: Accuracy::Hundredths,
35        }
36    }
37
38    /// Creates a new Segment Time Formatter that uses the accuracy provided for
39    /// showing the fractional part.
40    pub const fn with_accuracy(accuracy: Accuracy) -> Self {
41        SegmentTime { accuracy }
42    }
43}
44
45impl Default for SegmentTime {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl TimeFormatter<'_> for SegmentTime {
52    type Inner = Inner;
53
54    fn format<T>(&self, time: T) -> Self::Inner
55    where
56        T: Into<Option<TimeSpan>>,
57    {
58        Inner {
59            time: time.into(),
60            accuracy: self.accuracy,
61        }
62    }
63}
64
65impl Display for Inner {
66    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
67        if let Some(time) = self.time {
68            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
69            let (total_seconds, nanoseconds) = if (total_seconds | nanoseconds as i64) < 0 {
70                f.write_str(MINUS)?;
71                ((-total_seconds) as u64, (-nanoseconds) as u32)
72            } else {
73                (total_seconds as u64, nanoseconds as u32)
74            };
75            // These are intentionally not data dependent, such that the CPU can
76            // calculate all of them in parallel. On top of that they are
77            // integer divisions of known constants, which get turned into
78            // multiplies and shifts, which is very fast.
79            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
80            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
81            let hours = total_seconds / SECONDS_PER_HOUR;
82
83            let mut buffer = itoa::Buffer::new();
84
85            if hours > 0 {
86                f.write_str(buffer.format(hours))?;
87                f.write_str(":")?;
88                f.write_str(format_padded(minutes))?;
89                f.write_str(":")?;
90                f.write_str(format_padded(seconds))?;
91            } else if minutes > 0 {
92                f.write_str(buffer.format(minutes))?;
93                f.write_str(":")?;
94                f.write_str(format_padded(seconds))?;
95            } else {
96                f.write_str(buffer.format(seconds))?;
97            }
98            self.accuracy.format_nanoseconds(nanoseconds).fmt(f)
99        } else {
100            f.write_str(DASH)
101        }
102    }
103}
104
105#[test]
106fn test() {
107    let time = "4:20.69".parse::<TimeSpan>().unwrap();
108    let formatted = SegmentTime::new().format(time).to_string();
109    assert_eq!(formatted, "4:20.69");
110}