Skip to main content

deep_time/t_span/
mod.rs

1use crate::{ATTOS_PER_SEC, Dt, GregorianTime, Scale};
2
3mod arithmetic;
4mod constructors;
5mod formatting;
6mod from_str;
7mod ops;
8pub mod time_units;
9
10#[cfg(feature = "alloc")]
11mod to_str;
12
13#[cfg(feature = "hifitime")]
14mod from_hifitime;
15#[cfg(feature = "hifitime")]
16mod to_hifitime;
17
18#[cfg(feature = "chrono")]
19mod from_chrono;
20#[cfg(feature = "chrono")]
21mod to_chrono;
22
23#[cfg(feature = "jiff")]
24mod from_jiff;
25#[cfg(feature = "jiff")]
26mod to_jiff;
27
28/// A high-precision **duration** (time span) expressed as **seconds + attoseconds**
29/// (where 1 attosecond = 10⁻¹⁸ s).
30///
31/// `TSpan` is the time span counterpart of `Dt`.
32///
33/// - Precision: 10⁻¹⁸ s
34/// - Range: ±~292 billion years (i64 seconds limit).
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[cfg_attr(feature = "js", derive(tsify::Tsify))]
37#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct TSpan {
39    /// Signed whole seconds.
40    pub sec: i64,
41    /// Fractional part in attoseconds (`0 ≤ attos < 10¹⁸`).
42    pub attos: u64,
43}
44
45impl TSpan {
46    /// Seconds field getter.
47    #[inline]
48    pub const fn sec(&self) -> i64 {
49        self.sec
50    }
51
52    /// Subseconds field getter (attoseconds).
53    #[inline]
54    pub const fn attos(&self) -> u64 {
55        self.attos
56    }
57
58    /// Normalizes the representation so that the attosecond part lies in the range `[0, ATTOS_PER_SEC)`.
59    #[inline]
60    pub const fn carry_over(&mut self) -> &mut Self {
61        if self.attos >= ATTOS_PER_SEC {
62            self.sec += (self.attos / ATTOS_PER_SEC) as i64;
63            self.attos %= ATTOS_PER_SEC;
64        }
65        self
66    }
67
68    #[inline]
69    pub const fn to_tai(&self, current: Scale) -> Dt {
70        Dt::from(self.sec, self.attos, current)
71    }
72
73    #[inline]
74    pub const fn to(&self, current: Scale, target: Scale) -> TSpan {
75        Dt::from(self.sec, self.attos, current).to(target)
76    }
77
78    #[inline]
79    pub const fn to_gregorian_time(&self, current: Scale) -> GregorianTime {
80        Dt::from(self.sec, self.attos, current).to_gregorian_time()
81    }
82
83    #[inline]
84    pub const fn to_epoch(&self, epoch: Dt, current: Scale) -> Self {
85        Dt::from(self.sec, self.attos, current).to_epoch(epoch, current)
86    }
87}
88
89impl Default for TSpan {
90    fn default() -> Self {
91        Self::ZERO
92    }
93}
94
95#[cfg(feature = "wire")]
96impl TSpan {
97    /// Current wire format version.
98    pub const WIRE_VERSION: u8 = 1;
99
100    /// Size of the canonical wire representation in bytes (17 bytes).
101    pub const WIRE_SIZE: usize = 17;
102
103    /// Serializes this `TSpan` into a fixed 17-byte little-endian buffer.
104    ///
105    /// # Wire Format
106    ///
107    /// - Byte `0`: Version (`WIRE_VERSION`)
108    /// - Bytes `[1..9]`: `sec` as little-endian `i64`
109    /// - Bytes `[9..17]`: `attos` as little-endian `u64`
110    pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
111        let mut buf = [0u8; Self::WIRE_SIZE];
112        buf[0] = Self::WIRE_VERSION;
113        buf[1..9].copy_from_slice(&self.sec.to_le_bytes());
114        buf[9..17].copy_from_slice(&self.attos.to_le_bytes());
115        buf
116    }
117
118    /// Deserializes a `TSpan` from exactly 17 bytes of wire data.
119    ///
120    /// Returns `None` if the version byte is unknown.
121    /// Any `attos` value ≥ 10¹⁸ is automatically normalized using
122    /// [`carry_over`](Self::carry_over) so the resulting `TSpan`
123    /// is always in canonical form.
124    ///
125    /// ## Security
126    ///
127    /// Safe to call with completely untrusted input. Fixed-size format,
128    /// no allocation, no `unsafe`, and no possibility of code execution.
129    pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
130        if bytes.len() != Self::WIRE_SIZE {
131            return None;
132        }
133
134        // Version check for future compatibility
135        if bytes[0] != Self::WIRE_VERSION {
136            return None;
137        }
138
139        let sec = i64::from_le_bytes([
140            bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
141        ]);
142        let attos = u64::from_le_bytes([
143            bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16],
144        ]);
145
146        Some(Self::new(sec, attos))
147    }
148}