devela/phys/time/
fmt.rs

1// devela::phys::time::fmt
2//
3//!
4//
5// IMPROVE:
6// - secs_f64: make a version with fixed format, and another custom reducible.
7// - nanos_u64: to not show leading zeros for seconds.
8// - nanos_u64: not just clamp the seconds but all to 999?
9//
10// TODO:
11// - FromStr for split, like this:
12//   - https://github.com/toml-rs/toml/blob/main/crates/toml_datetime/src/datetime.rs
13
14#[allow(unused_imports)]
15#[cfg(feature = "_float_f64")]
16use crate::ExtFloat;
17#[cfg(feature = "alloc")]
18use crate::{format, String};
19#[cfg(feature = "_str_u8")]
20use crate::{format_buf, Ascii, StringU8};
21use crate::{NoTime, TimeSplit, TimeSplitHourNano};
22
23/// Timecode splitting and formatting.
24///
25/// # Examples
26/// ```
27/// # use devela::Timecode;
28/// #[cfg(feature = "_str_u8")]
29/// #[cfg(any(feature = "std", feature = "_float_f64"))]
30/// assert_eq!(Timecode::secs_f64(3661.5), "01:01:01.500");
31///
32/// #[cfg(feature = "_str_u8")]
33/// assert_eq!(Timecode::nanos_u64(1_002_003_004), "001s 002ms 003µs 004ns");
34/// ```
35pub struct Timecode;
36
37impl Timecode {
38    /* time splitting */
39
40    /// Decomposes a number of `seconds` in `{ h, m, s, ms }`.
41    ///
42    /// The maximum decomposition for [`u64::MAX`] is
43    /// `{ h: 5_124_095_576_030_431, .. }` (more than 584_942_417 millenia).
44    // -> 64 bits
45    #[must_use]
46    #[cfg(any(feature = "std", feature = "_float_f64"))]
47    #[cfg_attr(feature = "nightly_doc", doc(cfg(any(feature = "std", feature = "_float_f64"))))]
48    pub fn split_secs_f64(seconds: f64) -> TimeSplitHourNano<u32, u8, u8, u16, NoTime, NoTime> {
49        let ms = (seconds.fract() * 1000.) as u16;
50        let mut ts = seconds.trunc() as u64;
51        let h = (ts / 3600) as u32;
52        ts %= 3600;
53        let m = (ts / 60) as u8;
54        let s = (ts % 60) as u8;
55        TimeSplit::new_hour_nano(h, m, s, ms, (), ())
56    }
57
58    /// Splits a number of `nanoseconds` in `{ s, ms, µs, ns }`.
59    ///
60    /// The maximum decomposition for [`u64::MAX`] is
61    /// `{ s: 1_266_874_889, .. }` (more than 40 years).
62    // -> 80 bits
63    #[must_use]
64    pub const fn split_nanos_u64(
65        nanos: u64,
66    ) -> TimeSplitHourNano<NoTime, NoTime, u32, u16, u16, u16> {
67        let (us_tmp, ns) = (nanos / 1000, (nanos % 1000) as u16);
68        let (ms_tmp, us) = (us_tmp / 1000, (us_tmp % 1000) as u16);
69        let (s, ms) = ((ms_tmp / 1000) as u32, (ms_tmp % 1000) as u16);
70        TimeSplit::new_hour_nano((), (), s, ms, us, ns)
71    }
72
73    /// Splits a number of `nanoseconds` in `{ s, ms, µs, ns }`.
74    ///
75    /// The maximum decomposition for [`u32::MAX`] is
76    /// `{ ns: 295, µs: 967, ms: 294, s: 4 }` (more than 4 seconds).
77    // -> 56 bits
78    #[must_use]
79    pub const fn split_nanos_u32(
80        nanos: u32,
81    ) -> TimeSplitHourNano<NoTime, NoTime, u8, u16, u16, u16> {
82        let (us_tmp, ns) = (nanos / 1000, (nanos % 1000) as u16);
83        let (ms_tmp, us) = (us_tmp / 1000, (us_tmp % 1000) as u16);
84        let (s, ms) = ((ms_tmp / 1000) as u8, (ms_tmp % 1000) as u16);
85        TimeSplit::new_hour_nano((), (), s, ms, us, ns)
86    }
87
88    /// Returns the time code as `HH:MM:SS:MIL` or `MM:SS:MIL`.
89    ///
90    /// The hours are clamped to 99 (more than 4 days).
91    ///
92    /// # Features
93    /// Makes use of the `unsafe_str` feature if enabled.
94    //
95    // -> 96 bits
96    #[cfg(any(feature = "std", feature = "_float_f64"))]
97    #[cfg_attr(feature = "nightly_doc", doc(cfg(any(feature = "std", feature = "_float_f64"))))]
98    #[cfg(feature = "_str_u8")]
99    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "_str_u8")))]
100    pub fn secs_f64(seconds: f64) -> StringU8<12> {
101        let TimeSplitHourNano { h, m, s, ms, .. } = Self::split_secs_f64(seconds);
102        let m = Ascii(m as u32).digits_str(2);
103        let s = Ascii(s as u32).digits_str(2);
104        let ms = Ascii(ms as u32).digits_str(3);
105
106        let mut buf = [0; 12];
107        let mut buf_len = 12;
108
109        if h > 0 {
110            let h = Ascii(h.min(99)).digits_str(2);
111            let _str = format_buf![&mut buf, "{h}:{m}:{s}.{ms}"];
112        } else {
113            buf_len = 9;
114            let _str = format_buf![&mut buf, "{m}:{s}.{ms}"];
115        }
116
117        #[cfg(any(feature = "safe_time", not(feature = "unsafe_str")))]
118        return StringU8::<12>::from_bytes_nleft(buf, buf_len).unwrap();
119
120        #[cfg(all(not(feature = "safe_time"), feature = "unsafe_str"))]
121        // SAFETY: the buffer contains only ASCII characters.
122        unsafe {
123            StringU8::<12>::from_bytes_nleft_unchecked(buf, buf_len)
124        }
125    }
126
127    /// Returns the time code, up to seconds, as `001s 012ms 012µs 012ns`.
128    ///
129    /// The seconds are clamped to 999 (more than 16 minutes).
130    #[cfg(feature = "alloc")]
131    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "alloc")))]
132    // -> 192 bits in "64", 92 bits in "32"
133    pub fn nanos_u64_alloc(ns: u64) -> String {
134        let (us, ns_rem) = (ns / 1_000, ns % 1_000);
135        let (ms, us_rem) = (us / 1_000, us % 1_000);
136        let (s, ms_rem) = (ms / 1_000, ms % 1_000);
137        let s = s.min(999);
138
139        if s > 0 {
140            format!["{s}s {ms_rem:03}ms {us_rem:03}µs {ns_rem:03}ns"]
141        } else if ms > 0 {
142            format!["{ms_rem}ms {us_rem:03}µs {ns_rem:03}ns"]
143        } else if us > 0 {
144            format!["{us_rem}µs {ns_rem:03}ns"]
145        } else {
146            format!["{ns_rem:03}ns"]
147        }
148    }
149
150    /// Returns the time code, up to seconds, as `001s 012ms 012µs 012345ns`.
151    ///
152    /// The seconds are clamped to 999 (more than 16 minutes).
153    // -> 208 bits
154    #[cfg(feature = "_str_u8")]
155    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "_str_u8")))]
156    pub fn nanos_u64(nanos: u64) -> StringU8<23> {
157        let TimeSplitHourNano { s, ms, us, ns, .. } = Self::split_nanos_u64(nanos);
158        let s_str = Ascii(s.min(999)).digits_str(3);
159        let ms_str = Ascii(ms as u32).digits_str(3);
160        let us_str = Ascii(us as u32).digits_str(3);
161        let ns_str = Ascii(ns as u32).digits_str(3);
162
163        let mut buf = [0; 23];
164        let mut buf_len = 23; // = 18 + 3digits + 1name(s) + 1space
165
166        if s > 0 {
167            let _ = format_buf![&mut buf, "{s_str}s {ms_str}ms {us_str}µs {ns_str}ns"];
168        } else if ms > 0 {
169            buf_len = 18; // = 18 + 3digits + 2name(ms) + 1space
170            let _ = format_buf![&mut buf, "{ms_str}ms {us_str}µs {ns_str}ns"];
171        } else if us > 0 {
172            buf_len = 12; // = 5 + 3digits + 3name(µs) + 1space
173            let _ = format_buf![&mut buf, "{us_str}µs {ns_str}ns"];
174        } else {
175            buf_len = 5; // = 0 + 3digits + 2name(ns)
176            let _ = format_buf![&mut buf, "{ns_str}ns"];
177        }
178
179        #[cfg(any(feature = "safe_time", not(feature = "unsafe_str")))]
180        return StringU8::<23>::from_bytes_nleft(buf, buf_len).unwrap();
181
182        #[cfg(all(not(feature = "safe_time"), feature = "unsafe_str"))]
183        // SAFETY: the buffer contains only ASCII characters.
184        unsafe {
185            StringU8::<23>::from_bytes_nleft_unchecked(buf, buf_len)
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    #[cfg(any(feature = "std", feature = "_float_f64"))]
196    fn timecode_split_secs_f64() {
197        let result = Timecode::split_secs_f64(3661.500);
198        assert_eq!(8, size_of_val(&result)); //
199        assert_eq!(result, TimeSplit::new_hour_nano(1, 1, 1, 500, (), ()));
200    }
201
202    #[test]
203    fn timecode_split_nanos_u64() {
204        let result = Timecode::split_nanos_u64(1_002_003_004);
205        assert_eq!(12, size_of_val(&result));
206        assert_eq!(result, TimeSplit::new_hour_nano((), (), 1, 2, 3, 4));
207    }
208
209    #[test]
210    fn timecode_split_nanos_u32() {
211        let result = Timecode::split_nanos_u32(1_002_003);
212        assert_eq!(8, size_of_val(&result));
213        assert_eq!(result, TimeSplit::new_hour_nano((), (), 0, 1, 2, 3));
214    }
215
216    #[test]
217    #[cfg(feature = "_str_u8")]
218    #[cfg(any(feature = "std", feature = "_float_f64"))]
219    fn timecode_secs_f64() {
220        let formatted = Timecode::secs_f64(3661.5);
221        assert_eq!(formatted, "01:01:01.500");
222    }
223
224    #[test]
225    #[cfg(feature = "alloc")]
226    fn timecode_nanos_u64_alloc() {
227        let formatted = Timecode::nanos_u64_alloc(1_002_003_004);
228        assert_eq!(formatted, "1s 002ms 003µs 004ns");
229    }
230
231    #[test]
232    #[cfg(feature = "_str_u8")]
233    fn timecode_nanos_u64() {
234        let formatted = Timecode::nanos_u64(1_002_003_004);
235        assert_eq!(formatted, "001s 002ms 003µs 004ns");
236    }
237}