Skip to main content

deep_time/dt/
mod.rs

1mod arithmetic;
2mod constructors;
3mod conveniences;
4mod conversions;
5mod decimal_year;
6mod from_ccsds;
7mod from_str;
8mod gregorian;
9mod julian_date;
10mod ops;
11mod tdb;
12mod to_bin_ccsds;
13mod to_str;
14
15pub mod lunar;
16pub mod numbers_traits;
17pub mod trajectory;
18
19#[cfg(feature = "alloc")]
20mod formatting;
21#[cfg(feature = "alloc")]
22mod to_str_ccsds;
23
24#[cfg(feature = "mars")]
25pub mod mars;
26
27#[cfg(feature = "hifitime")]
28mod hifitime;
29
30#[cfg(feature = "chrono")]
31mod chrono;
32
33#[cfg(feature = "jiff")]
34mod jiff;
35
36use crate::ATTOS_PER_SEC;
37use core::fmt;
38
39/// ## [`Dt`] A high-precision instant or duration with attosecond resolution.
40///
41/// This is the core time type of the library. It represents both absolute
42/// instants and durations using the same compact representation, making it
43/// convenient anywhere precise time measurement or arithmetic is needed.
44///
45/// ## Representation
46///
47/// It stores:
48///
49/// - `sec: i64` — whole seconds (signed).
50/// - `attos: u64` — fractional seconds in attoseconds (`0 ≤ attos < 10¹⁸`).
51///     - These always push the `Dt` towards the positive.
52///
53/// This gives a resolution of one attosecond while supporting a range of
54/// roughly ±292 billion years. An [`i128`] was considered but decided against
55/// due to the difficulty of math without overflow.
56///
57/// There are many different ways to go to and from a `Dt` see the [`documentation`](../struct.Dt.html)
58/// for the full list of methods.
59///
60/// It implements `Copy` and `Clone`. Optional derives for `serde` and
61/// `tsify` are available behind the corresponding features.
62///
63/// ## Reference epoch and scales
64///
65/// When using the conversion functions [`Dt::to`] and [`Dt::from`] the
66/// epoch for **all** time scales is [`Dt::ZERO`] 2000-01-01 noon.
67///
68/// Many convenience constructors and accessors exist for common epochs
69/// (UNIX, GPS, Galileo, BeiDou, CXC, 1977 TT/TCG/TCB, etc.).
70///
71/// See the [`Scale`] documentation for the complete list of supported scales,
72/// leap-second handling, historical UTC models, relativistic coordinate times
73/// (TCG, TCB), and the lunar scales LTC / TCL (based on the LTE440 model).
74///
75/// ## Arithmetic and manipulation
76///
77/// `Dt` provides rich const-friendly arithmetic:
78///
79/// - Addition and subtraction of durations
80/// - Multiplication and division by integers or `Real` (f64)
81/// - `floor`, `ceil`, `round` to an arbitrary unit
82/// - Many convenience increment/decrement methods (`add_1ns`, `sub_ms`, …)
83/// - Signed difference via [`to_diff_raw`](Self::to_diff_raw)
84///
85/// Relativistic proper-time corrections and clock-drift models are supported
86/// via [`convert_using_drift`](Self::convert_using_drift) and related methods.
87///
88/// ## Notes
89///
90/// - `Dt` does **not** store a time scale internally. The scale is always
91///   an explicit parameter of conversion and construction methods.
92/// - Leap-second handling follows the chosen `Scale` (UTC, UTCSpice, UTCSofa).
93#[derive(Clone, Copy)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95#[cfg_attr(feature = "js", derive(tsify::Tsify))]
96pub struct Dt {
97    pub sec: i64,
98    pub attos: u64,
99}
100
101impl Dt {
102    /// Normalizes the representation so that the attosecond part lies in the range `[0, ATTOS_PER_SEC)`.
103    #[inline]
104    pub const fn carry_attos_mut(&mut self) -> &mut Self {
105        if self.attos >= ATTOS_PER_SEC {
106            self.sec = self.sec.saturating_add((self.attos / ATTOS_PER_SEC) as i64);
107            self.attos %= ATTOS_PER_SEC;
108        }
109        self
110    }
111
112    /// Normalizes the representation so that the attosecond part lies in the range `[0, ATTOS_PER_SEC)`.
113    #[inline]
114    pub const fn carry_attos(&self) -> Self {
115        if self.attos < ATTOS_PER_SEC {
116            return *self;
117        }
118        Self {
119            sec: self.sec.saturating_add((self.attos / ATTOS_PER_SEC) as i64),
120            attos: self.attos % ATTOS_PER_SEC,
121        }
122    }
123}
124
125impl Default for Dt {
126    fn default() -> Self {
127        Self::ZERO
128    }
129}
130
131impl fmt::Display for Dt {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        let sec = self.sec;
134        let attos = self.attos;
135
136        // Default to nanosecond precision (9 digits) — most useful for everyday use
137        let precision = f.precision().unwrap_or(9);
138
139        // Respect the `+` sign when the user writes {:+}
140        if f.sign_plus() && sec >= 0 {
141            write!(f, "+")?;
142        }
143
144        write!(f, "{}", sec)?;
145
146        if precision > 0 {
147            let prec = precision.min(18);
148            let scale = 10u64.pow(18 - prec as u32);
149            let value = attos / scale;
150            write!(f, ".{:0>width$}", value, width = prec)?;
151        }
152
153        Ok(())
154    }
155}
156
157impl fmt::Debug for Dt {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        f.debug_struct("Dt")
160            .field("sec", &self.sec)
161            .field("attos", &self.attos)
162            .finish()
163    }
164}