Skip to main content

gix_date/time/
write.rs

1use crate::{SecondsSinceUnixEpoch, Time};
2
3/// Serialize this instance as string, similar to what [`write_to()`](Self::write_to()) would do.
4impl std::fmt::Display for Time {
5    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6        let mut buf = Vec::with_capacity(Time::MAX.size());
7        self.write_to(&mut buf).expect("write to memory cannot fail");
8        // Time serializes as ASCII, which is a subset of UTF-8.
9        // Use `from_utf8()` (validated) instead of `from_utf8_unchecked()` for safety,
10        // with the option to switch to an unsafe version if 30% performance boost are needed here.
11        let raw = std::str::from_utf8(&buf).expect("time serializes as valid UTF-8");
12        f.write_str(raw)
13    }
14}
15
16/// Serialization with standard `git` format
17impl Time {
18    /// Serialize this instance to `out` in a format suitable for use in header fields of serialized git commits or tags.
19    pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
20        const SECONDS_PER_HOUR: u32 = 60 * 60;
21        let offset = self.offset.unsigned_abs();
22        let hours = offset / SECONDS_PER_HOUR;
23        let minutes = (offset - (hours * SECONDS_PER_HOUR)) / 60;
24
25        if hours > 99 {
26            return Err(std::io::Error::other("Cannot represent offsets larger than +-9900"));
27        }
28
29        let mut itoa = itoa::Buffer::new();
30        out.write_all(itoa.format(self.seconds).as_bytes())?;
31        out.write_all(b" ")?;
32        out.write_all(if self.offset < 0 { b"-" } else { b"+" })?;
33
34        const ZERO: &[u8; 1] = b"0";
35
36        if hours < 10 {
37            out.write_all(ZERO)?;
38        }
39        out.write_all(itoa.format(hours).as_bytes())?;
40
41        if minutes < 10 {
42            out.write_all(ZERO)?;
43        }
44        out.write_all(itoa.format(minutes).as_bytes()).map(|_| ())
45    }
46
47    /// Computes the number of bytes necessary to write it using [`Time::write_to()`].
48    pub const fn size(&self) -> usize {
49        let is_negative = self.seconds < 0;
50        Self::count_positive_digits(self.seconds.unsigned_abs()) + is_negative as usize + 6
51        // space + offset sign + hours (2) + minutes (2)
52    }
53
54    /// Count the number of decimal digits in a positive integer.
55    const fn count_positive_digits(n: u64) -> usize {
56        // Powers of 10 for comparison
57        const POW10: [u64; 20] = [
58            1,
59            10,
60            100,
61            1_000,
62            10_000,
63            100_000,
64            1_000_000,
65            10_000_000,
66            100_000_000,
67            1_000_000_000,
68            10_000_000_000,
69            100_000_000_000,
70            1_000_000_000_000,
71            10_000_000_000_000,
72            100_000_000_000_000,
73            1_000_000_000_000_000,
74            10_000_000_000_000_000,
75            100_000_000_000_000_000,
76            1_000_000_000_000_000_000,
77            10_000_000_000_000_000_000,
78        ];
79
80        // Binary search would be nice but not const-fn friendly, so use simple loop
81        let mut digits = 1;
82        while digits < 20 && n >= POW10[digits] {
83            digits += 1;
84        }
85        digits
86    }
87
88    /// The numerically largest possible time instance, whose [size()](Time::size) is the largest possible
89    /// number of bytes to write using [`Time::write_to()`].
90    pub const MAX: Time = Time {
91        seconds: SecondsSinceUnixEpoch::MAX,
92        offset: 99 * 60 * 60 + 59 * 60 + 59,
93    };
94}