livesplit_core/timing/formatter/
complete.rs

1use super::{
2    format_padded, TimeFormatter, ASCII_MINUS, SECONDS_PER_DAY, SECONDS_PER_HOUR,
3    SECONDS_PER_MINUTE,
4};
5use crate::TimeSpan;
6use core::fmt::{Display, Formatter, Result};
7
8pub struct Inner(Option<TimeSpan>);
9
10/// The Complete Time Formatter formats a [`TimeSpan`] in a way that preserves as
11/// much information as possible. The hours and minutes are always shown and a
12/// fractional part of 9 digits is used. If there's >24h, then a day prefix is
13/// attached (with the hours wrapping around to 0): `dd.hh:mm:ss.fffffffff`
14///
15/// This formatter uses an ASCII minus for negative times and shows a zero time
16/// for empty times.
17///
18/// # Example Formatting
19///
20/// * Empty Time `00:00:00.000000000`
21/// * Seconds `00:00:23.123400000`
22/// * Minutes `00:12:34.987654321`
23/// * Hours `12:34:56.123456789`
24/// * Negative Times `-12:34:56.123456789`
25/// * Days `89.12:34:56.123456789`
26#[derive(Default)]
27pub struct Complete;
28
29impl Complete {
30    /// Creates a new Complete Time Formatter.
31    pub const fn new() -> Self {
32        Complete
33    }
34}
35
36impl TimeFormatter<'_> for Complete {
37    type Inner = Inner;
38
39    fn format<T>(&self, time: T) -> Self::Inner
40    where
41        T: Into<Option<TimeSpan>>,
42    {
43        Inner(time.into())
44    }
45}
46
47impl Display for Inner {
48    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
49        if let Some(time) = self.0 {
50            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
51            let (total_seconds, nanoseconds) = if (total_seconds | nanoseconds as i64) < 0 {
52                // Since, this Formatter is used for writing out split files, we
53                // have to use an ASCII Minus here.
54                f.write_str(ASCII_MINUS)?;
55                ((-total_seconds) as u64, (-nanoseconds) as u32)
56            } else {
57                (total_seconds as u64, nanoseconds as u32)
58            };
59            // These are intentionally not data dependent, such that the CPU can
60            // calculate all of them in parallel. On top of that they are
61            // integer divisions of known constants, which get turned into
62            // multiplies and shifts, which is very fast.
63            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
64            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
65            let hours = ((total_seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR) as u8;
66            let days = total_seconds / SECONDS_PER_DAY;
67
68            let mut buffer = itoa::Buffer::new();
69
70            if days > 0 {
71                f.write_str(buffer.format(days))?;
72                f.write_str(".")?;
73            }
74
75            f.write_str(format_padded(hours))?;
76            f.write_str(":")?;
77            f.write_str(format_padded(minutes))?;
78            f.write_str(":")?;
79            f.write_str(format_padded(seconds))?;
80            f.write_str(".")?;
81            let nanoseconds = buffer.format(nanoseconds);
82            f.write_str(&"000000000"[nanoseconds.len()..])?;
83            f.write_str(nanoseconds)
84        } else {
85            f.write_str("00:00:00.000000000")
86        }
87    }
88}