livesplit_core/timing/formatter/
days.rs

1use super::{
2    format_padded, TimeFormatter, MINUS, SECONDS_PER_DAY, 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}
10
11/// The Days Time Formatter formats a [`TimeSpan`] so that times >24h are prefixed
12/// with the amount of days, wrapping the hours around to 0. There's no
13/// fractional part for times. The minutes are always shown.
14///
15/// # Example Formatting
16///
17/// * Empty Time `0:00`
18/// * Seconds `0:23`
19/// * Minutes `12:34`
20/// * Hours `12:34:56`
21/// * Negative Times `−12:34:56`
22/// * Days `89d 12:34:56`
23/// * Negative Days `−89d 12:34:56`
24#[derive(Default)]
25pub struct Days;
26
27impl Days {
28    /// Creates a new Days Time Formatter.
29    pub const fn new() -> Self {
30        Days
31    }
32}
33
34impl TimeFormatter<'_> for Days {
35    type Inner = Inner;
36
37    fn format<T>(&self, time: T) -> Self::Inner
38    where
39        T: Into<Option<TimeSpan>>,
40    {
41        Inner { time: time.into() }
42    }
43}
44
45impl Display for Inner {
46    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
47        if let Some(time) = self.time {
48            let (total_seconds, nanoseconds) = time.to_seconds_and_subsec_nanoseconds();
49            let total_seconds = if (total_seconds | nanoseconds as i64) < 0 {
50                f.write_str(MINUS)?;
51                (-total_seconds) as u64
52            } else {
53                total_seconds as u64
54            };
55            // These are intentionally not data dependent, such that the CPU can
56            // calculate all of them in parallel. On top of that they are
57            // integer divisions of known constants, which get turned into
58            // multiplies and shifts, which is very fast.
59            let seconds = (total_seconds % SECONDS_PER_MINUTE) as u8;
60            let minutes = ((total_seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
61            let hours = ((total_seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR) as u8;
62            let days = total_seconds / SECONDS_PER_DAY;
63
64            let mut buffer = itoa::Buffer::new();
65
66            if days > 0 {
67                f.write_str(buffer.format(days))?;
68                f.write_str("d ")?;
69            }
70
71            if days > 0 || hours > 0 {
72                f.write_str(buffer.format(hours))?;
73                f.write_str(":")?;
74                f.write_str(format_padded(minutes))?;
75            } else {
76                f.write_str(buffer.format(minutes))?;
77            }
78            f.write_str(":")?;
79            f.write_str(format_padded(seconds))
80        } else {
81            f.write_str("0:00")
82        }
83    }
84}