Skip to main content

lox_core/time/
subsecond.rs

1// SPDX-FileCopyrightText: 2023 Andrei Zisu <matzipan@gmail.com>
2// SPDX-FileCopyrightText: 2023 Angus Morrison <github@angus-morrison.com>
3// SPDX-FileCopyrightText: 2023 Helge Eichhorn <git@helgeeichhorn.de>
4//
5// SPDX-License-Identifier: MPL-2.0
6
7//! The [Subsecond] newtype for working with fractions of seconds.
8//!
9//! This module provides a high-precision representation of subsecond time values
10//! with attosecond (10⁻¹⁸ second) resolution. The representation uses six components
11//! to store milliseconds, microseconds, nanoseconds, picoseconds, femtoseconds, and
12//! attoseconds, each normalized to the range [0, 999].
13//!
14//! # Examples
15//!
16//! ```
17//! use lox_core::time::subsecond::Subsecond;
18//!
19//! // Create from individual components
20//! let s = Subsecond::new()
21//!     .set_milliseconds(123)
22//!     .set_microseconds(456)
23//!     .set_nanoseconds(789);
24//!
25//! assert_eq!(s.as_attoseconds(), 123456789000000000);
26//!
27//! // Create from total attoseconds
28//! let s = Subsecond::from_attoseconds(123456789123456789);
29//! assert_eq!(s.milliseconds(), 123);
30//! assert_eq!(s.microseconds(), 456);
31//!
32//! // Parse from string
33//! let s: Subsecond = "123456".parse().unwrap();
34//! assert_eq!(s.milliseconds(), 123);
35//! assert_eq!(s.microseconds(), 456);
36//! ```
37
38use std::fmt::Display;
39use std::str::FromStr;
40
41use crate::f64::consts::SECONDS_PER_ATTOSECOND;
42use crate::i64::consts::{
43    ATTOSECONDS_IN_FEMTOSECOND, ATTOSECONDS_IN_MICROSECOND, ATTOSECONDS_IN_MILLISECOND,
44    ATTOSECONDS_IN_NANOSECOND, ATTOSECONDS_IN_PICOSECOND, ATTOSECONDS_IN_SECOND,
45};
46use thiserror::Error;
47
48const FACTORS: [i64; 6] = [
49    ATTOSECONDS_IN_MILLISECOND,
50    ATTOSECONDS_IN_MICROSECOND,
51    ATTOSECONDS_IN_NANOSECOND,
52    ATTOSECONDS_IN_PICOSECOND,
53    ATTOSECONDS_IN_FEMTOSECOND,
54    1,
55];
56
57/// A high-precision representation of subsecond time with attosecond resolution.
58///
59/// `Subsecond` stores time values less than one second using six components:
60/// milliseconds, microseconds, nanoseconds, picoseconds, femtoseconds, and attoseconds.
61/// Each component is normalized to the range [0, 999].
62///
63/// The total precision is 10⁻¹⁸ seconds (one attosecond), providing sufficient accuracy
64/// for astronomical and high-precision timing applications.
65#[derive(Debug, Default, Clone, Copy)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub struct Subsecond([u32; 6]);
68
69impl Subsecond {
70    /// A constant representing zero subsecond time.
71    pub const ZERO: Self = Self::new();
72
73    /// Creates a new `Subsecond` with all components set to zero.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use lox_core::time::subsecond::Subsecond;
79    ///
80    /// let s = Subsecond::new();
81    /// assert_eq!(s.as_attoseconds(), 0);
82    /// ```
83    pub const fn new() -> Self {
84        Self([0; 6])
85    }
86
87    /// Creates a `Subsecond` from a total number of attoseconds.
88    ///
89    /// The input value is automatically normalized to the range [0, 10¹⁸) attoseconds
90    /// (i.e., [0, 1) seconds). Values greater than or equal to one second wrap around,
91    /// and negative values wrap from the top.
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// use lox_core::time::subsecond::Subsecond;
97    ///
98    /// let s = Subsecond::from_attoseconds(123456789123456789);
99    /// assert_eq!(s.milliseconds(), 123);
100    /// assert_eq!(s.microseconds(), 456);
101    /// assert_eq!(s.nanoseconds(), 789);
102    ///
103    /// // Negative values wrap around
104    /// let s = Subsecond::from_attoseconds(-1);
105    /// assert_eq!(s.as_attoseconds(), 999999999999999999);
106    /// ```
107    pub const fn from_attoseconds(attoseconds: i64) -> Self {
108        let attoseconds_normalized = if attoseconds < 0 {
109            ATTOSECONDS_IN_SECOND + attoseconds
110        } else {
111            attoseconds
112        } as i128;
113        let mut this = Self::new();
114        this.0[0] = ((attoseconds_normalized / ATTOSECONDS_IN_MILLISECOND as i128) % 1000) as u32;
115        this.0[1] = ((attoseconds_normalized / ATTOSECONDS_IN_MICROSECOND as i128) % 1000) as u32;
116        this.0[2] = ((attoseconds_normalized / ATTOSECONDS_IN_NANOSECOND as i128) % 1000) as u32;
117        this.0[3] = ((attoseconds_normalized / ATTOSECONDS_IN_PICOSECOND as i128) % 1000) as u32;
118        this.0[4] = ((attoseconds_normalized / ATTOSECONDS_IN_FEMTOSECOND as i128) % 1000) as u32;
119        this.0[5] = (attoseconds_normalized % 1000) as u32;
120        this
121    }
122
123    /// Creates a `Subsecond` from an `f64` value, extracting the fractional part.
124    ///
125    /// Returns `None` if the value is not finite (NaN or infinite).
126    pub const fn from_f64(value: f64) -> Option<Self> {
127        if !value.is_finite() {
128            return None;
129        }
130        let rem = value % 1.0;
131        // Ensure remainder is in [0, 1) range (Rust's % can return negative values)
132        let rem = if rem < 0.0 { rem + 1.0 } else { rem };
133        // Convert to attoseconds with rounding to handle floating-point precision issues
134        let attoseconds = (rem / crate::f64::consts::SECONDS_PER_ATTOSECOND).round() as i64;
135        Some(Self::from_attoseconds(attoseconds))
136    }
137
138    /// Sets the millisecond component (10⁻³ seconds).
139    ///
140    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
141    /// In debug builds, values >= 1000 trigger an assertion.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use lox_core::time::subsecond::Subsecond;
147    ///
148    /// let s = Subsecond::new().set_milliseconds(123);
149    /// assert_eq!(s.milliseconds(), 123);
150    /// ```
151    pub const fn set_milliseconds(mut self, milliseconds: u32) -> Self {
152        debug_assert!(milliseconds < 1000);
153        self.0[0] = milliseconds % 1000;
154        self
155    }
156
157    /// Sets the microsecond component (10⁻⁶ seconds).
158    ///
159    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
160    /// In debug builds, values >= 1000 trigger an assertion.
161    pub const fn set_microseconds(mut self, microseconds: u32) -> Self {
162        debug_assert!(microseconds < 1000);
163        self.0[1] = microseconds % 1000;
164        self
165    }
166
167    /// Sets the nanosecond component (10⁻⁹ seconds).
168    ///
169    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
170    /// In debug builds, values >= 1000 trigger an assertion.
171    pub const fn set_nanoseconds(mut self, nanoseconds: u32) -> Self {
172        debug_assert!(nanoseconds < 1000);
173        self.0[2] = nanoseconds % 1000;
174        self
175    }
176
177    /// Sets the picosecond component (10⁻¹² seconds).
178    ///
179    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
180    /// In debug builds, values >= 1000 trigger an assertion.
181    pub const fn set_picoseconds(mut self, picoseconds: u32) -> Self {
182        debug_assert!(picoseconds < 1000);
183        self.0[3] = picoseconds % 1000;
184        self
185    }
186
187    /// Sets the femtosecond component (10⁻¹⁵ seconds).
188    ///
189    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
190    /// In debug builds, values >= 1000 trigger an assertion.
191    pub const fn set_femtoseconds(mut self, femtoseconds: u32) -> Self {
192        debug_assert!(femtoseconds < 1000);
193        self.0[4] = femtoseconds % 1000;
194        self
195    }
196
197    /// Sets the attosecond component (10⁻¹⁸ seconds).
198    ///
199    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
200    /// In debug builds, values >= 1000 trigger an assertion.
201    pub const fn set_attoseconds(mut self, attoseconds: u32) -> Self {
202        debug_assert!(attoseconds < 1000);
203        self.0[5] = attoseconds % 1000;
204        self
205    }
206
207    /// Converts the subsecond value to total attoseconds.
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// use lox_core::time::subsecond::Subsecond;
213    ///
214    /// let s = Subsecond::new().set_milliseconds(123).set_microseconds(456);
215    /// assert_eq!(s.as_attoseconds(), 123456000000000000);
216    /// ```
217    pub const fn as_attoseconds(&self) -> i64 {
218        self.0[0] as i64 * FACTORS[0]
219            + self.0[1] as i64 * FACTORS[1]
220            + self.0[2] as i64 * FACTORS[2]
221            + self.0[3] as i64 * FACTORS[3]
222            + self.0[4] as i64 * FACTORS[4]
223            + self.0[5] as i64 * FACTORS[5]
224    }
225
226    /// Converts the subsecond value to seconds as an `f64`.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use lox_core::time::subsecond::Subsecond;
232    ///
233    /// let s = Subsecond::new().set_milliseconds(500);
234    /// assert_eq!(s.as_seconds_f64(), 0.5);
235    /// ```
236    pub const fn as_seconds_f64(&self) -> f64 {
237        self.as_attoseconds() as f64 * SECONDS_PER_ATTOSECOND
238    }
239
240    /// Returns the millisecond component (10⁻³ seconds).
241    ///
242    /// The returned value is always in the range [0, 999].
243    pub const fn milliseconds(&self) -> u32 {
244        self.0[0]
245    }
246
247    /// Returns the microsecond component (10⁻⁶ seconds).
248    ///
249    /// The returned value is always in the range [0, 999].
250    pub const fn microseconds(&self) -> u32 {
251        self.0[1]
252    }
253
254    /// Returns the nanosecond component (10⁻⁹ seconds).
255    ///
256    /// The returned value is always in the range [0, 999].
257    pub const fn nanoseconds(&self) -> u32 {
258        self.0[2]
259    }
260
261    /// Returns the picosecond component (10⁻¹² seconds).
262    ///
263    /// The returned value is always in the range [0, 999].
264    pub const fn picoseconds(&self) -> u32 {
265        self.0[3]
266    }
267
268    /// Returns the femtosecond component (10⁻¹⁵ seconds).
269    ///
270    /// The returned value is always in the range [0, 999].
271    pub const fn femtoseconds(&self) -> u32 {
272        self.0[4]
273    }
274
275    /// Returns the attosecond component (10⁻¹⁸ seconds).
276    ///
277    /// The returned value is always in the range [0, 999].
278    pub const fn attoseconds(&self) -> u32 {
279        self.0[5]
280    }
281}
282
283impl Ord for Subsecond {
284    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
285        self.as_attoseconds().cmp(&other.as_attoseconds())
286    }
287}
288
289impl PartialOrd for Subsecond {
290    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
291        Some(self.cmp(other))
292    }
293}
294
295impl PartialEq for Subsecond {
296    fn eq(&self, other: &Self) -> bool {
297        self.as_attoseconds() == other.as_attoseconds()
298    }
299}
300
301impl Eq for Subsecond {}
302
303const DIGITS: usize = 18;
304
305impl Display for Subsecond {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        "0.".fmt(f)?;
308        let mut s = self.as_attoseconds().to_string();
309        if s.len() < DIGITS {
310            s = format!("{:0>width$}", s, width = DIGITS);
311        }
312        let p = f.precision().unwrap_or(3).clamp(0, DIGITS);
313        s[0..p].fmt(f)
314    }
315}
316
317/// Error returned when parsing a `Subsecond` from a string fails.
318///
319/// This error occurs when the input string contains non-numeric characters
320/// or exceeds the maximum length of 18 digits.
321#[derive(Debug, Error)]
322#[error("could not parse subsecond from {0}")]
323pub struct SubsecondParseError(String);
324
325impl FromStr for Subsecond {
326    type Err = SubsecondParseError;
327
328    fn from_str(s: &str) -> Result<Self, Self::Err> {
329        let mut this = Self::default();
330
331        if s.is_empty() {
332            return Ok(this);
333        }
334
335        if s.chars().any(|c| !c.is_numeric()) {
336            return Err(SubsecondParseError(s.to_owned()));
337        }
338        let n = s.len();
339        if n > DIGITS {
340            return Err(SubsecondParseError(s.to_owned()));
341        }
342
343        let rem = n % 3;
344        let s = if rem != 0 {
345            let width = n + 3 - rem;
346            format!("{:0<width$}", s)
347        } else {
348            s.to_owned()
349        };
350
351        for i in (0..s.len()).step_by(3) {
352            this.0[i / 3] = s[i..i + 3].parse().unwrap();
353        }
354
355        Ok(this)
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362
363    #[test]
364    fn test_subsecond() {
365        let s = Subsecond::new()
366            .set_milliseconds(123)
367            .set_microseconds(456)
368            .set_nanoseconds(789)
369            .set_picoseconds(123)
370            .set_femtoseconds(456)
371            .set_attoseconds(789);
372
373        assert_eq!(s.as_attoseconds(), 123456789123456789);
374        assert_eq!(s.as_seconds_f64(), 0.1234567891234568);
375        assert_eq!(s.milliseconds(), 123);
376        assert_eq!(s.microseconds(), 456);
377        assert_eq!(s.nanoseconds(), 789);
378        assert_eq!(s.picoseconds(), 123);
379        assert_eq!(s.femtoseconds(), 456);
380        assert_eq!(s.attoseconds(), 789);
381    }
382
383    #[test]
384    fn test_subsecond_from_attoseconds() {
385        let s = Subsecond::from_attoseconds(123456789123456789);
386
387        assert_eq!(s.as_attoseconds(), 123456789123456789);
388        assert_eq!(s.as_seconds_f64(), 0.1234567891234568);
389        assert_eq!(s.milliseconds(), 123);
390        assert_eq!(s.microseconds(), 456);
391        assert_eq!(s.nanoseconds(), 789);
392        assert_eq!(s.picoseconds(), 123);
393        assert_eq!(s.femtoseconds(), 456);
394        assert_eq!(s.attoseconds(), 789);
395    }
396
397    #[test]
398    fn test_subsecond_display() {
399        let s = Subsecond::new()
400            .set_milliseconds(123)
401            .set_microseconds(456)
402            .set_nanoseconds(789)
403            .set_picoseconds(123)
404            .set_femtoseconds(456)
405            .set_attoseconds(789);
406
407        assert_eq!(format!("{}", s), "0.123");
408        assert_eq!(format!("{:.6}", s), "0.123456");
409        assert_eq!(format!("{:.18}", s), "0.123456789123456789");
410    }
411
412    #[test]
413    fn test_subsecond_parse() {
414        let exp = Subsecond::new().set_milliseconds(123).set_microseconds(400);
415        let act: Subsecond = "1234".parse().unwrap();
416        assert_eq!(act, exp);
417
418        let exp = Subsecond::new()
419            .set_milliseconds(123)
420            .set_microseconds(456)
421            .set_nanoseconds(789)
422            .set_picoseconds(123)
423            .set_femtoseconds(456)
424            .set_attoseconds(789);
425        let act: Subsecond = "123456789123456789".parse().unwrap();
426        assert_eq!(act, exp);
427    }
428
429    #[test]
430    #[should_panic]
431    fn test_subsecond_parse_error() {
432        "123foo".parse::<Subsecond>().unwrap();
433    }
434
435    #[test]
436    fn test_subsecond_from_attoseconds_negative() {
437        // -1 attosecond should wrap to 999999999999999999
438        let s = Subsecond::from_attoseconds(-1);
439        assert_eq!(s.as_attoseconds(), 999999999999999999);
440        assert_eq!(s.milliseconds(), 999);
441        assert_eq!(s.microseconds(), 999);
442        assert_eq!(s.nanoseconds(), 999);
443        assert_eq!(s.picoseconds(), 999);
444        assert_eq!(s.femtoseconds(), 999);
445        assert_eq!(s.attoseconds(), 999);
446    }
447
448    #[test]
449    fn test_subsecond_from_attoseconds_zero() {
450        let s = Subsecond::from_attoseconds(0);
451        assert_eq!(s.as_attoseconds(), 0);
452        assert_eq!(s, Subsecond::ZERO);
453    }
454
455    #[test]
456    fn test_subsecond_from_attoseconds_max() {
457        // Maximum subsecond value: 999999999999999999
458        let max = ATTOSECONDS_IN_SECOND - 1;
459        let s = Subsecond::from_attoseconds(max);
460        assert_eq!(s.as_attoseconds(), max);
461        assert_eq!(s.milliseconds(), 999);
462        assert_eq!(s.microseconds(), 999);
463        assert_eq!(s.nanoseconds(), 999);
464        assert_eq!(s.picoseconds(), 999);
465        assert_eq!(s.femtoseconds(), 999);
466        assert_eq!(s.attoseconds(), 999);
467    }
468
469    #[test]
470    fn test_subsecond_from_attoseconds_overflow() {
471        // Values >= 1 second should wrap around
472        let s = Subsecond::from_attoseconds(ATTOSECONDS_IN_SECOND);
473        assert_eq!(s.as_attoseconds(), 0);
474
475        let s = Subsecond::from_attoseconds(ATTOSECONDS_IN_SECOND + 123);
476        assert_eq!(s.as_attoseconds(), 123);
477    }
478
479    #[test]
480    fn test_subsecond_set_methods_max_value() {
481        // Test that 999 is preserved correctly
482        let s = Subsecond::new().set_milliseconds(999);
483        assert_eq!(s.milliseconds(), 999);
484
485        let s = Subsecond::new().set_microseconds(999);
486        assert_eq!(s.microseconds(), 999);
487
488        let s = Subsecond::new().set_nanoseconds(999);
489        assert_eq!(s.nanoseconds(), 999);
490    }
491
492    #[test]
493    fn test_subsecond_display_edge_cases() {
494        // Test zero
495        assert_eq!(format!("{}", Subsecond::ZERO), "0.000");
496
497        // Test precision of 0
498        let s = Subsecond::new().set_milliseconds(123);
499        assert_eq!(format!("{:.0}", s), "");
500
501        // Test precision larger than value
502        assert_eq!(format!("{:.25}", s), "0.123000000000000000");
503    }
504
505    #[test]
506    fn test_subsecond_parse_edge_cases() {
507        // Empty string
508        let s: Subsecond = "".parse().unwrap();
509        assert_eq!(s, Subsecond::ZERO);
510
511        // Single digit
512        let s: Subsecond = "1".parse().unwrap();
513        assert_eq!(s.milliseconds(), 100);
514
515        // Two digits
516        let s: Subsecond = "12".parse().unwrap();
517        assert_eq!(s.milliseconds(), 120);
518
519        // Maximum length (18 digits)
520        let s: Subsecond = "999999999999999999".parse().unwrap();
521        assert_eq!(s.as_attoseconds(), 999999999999999999);
522    }
523
524    #[test]
525    fn test_subsecond_parse_too_long() {
526        // 19 digits should fail
527        let result = "1234567890123456789".parse::<Subsecond>();
528        assert!(result.is_err());
529    }
530
531    #[test]
532    fn test_subsecond_ordering() {
533        let a = Subsecond::from_attoseconds(100);
534        let b = Subsecond::from_attoseconds(200);
535        let c = Subsecond::from_attoseconds(200);
536
537        assert!(a < b);
538        assert!(b > a);
539        assert_eq!(b, c);
540        assert!(b <= c);
541        assert!(b >= c);
542    }
543
544    #[test]
545    fn test_subsecond_equality() {
546        let a = Subsecond::new().set_milliseconds(123);
547        let b = Subsecond::from_attoseconds(123000000000000000);
548
549        assert_eq!(a, b);
550        assert_eq!(a.as_attoseconds(), b.as_attoseconds());
551    }
552}