Skip to main content

deep_time/dt/
mod.rs

1mod arithmetic;
2mod arithmetic_calendar;
3mod constructors;
4mod conveniences;
5mod conversions;
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 to_str_ccsds;
21
22#[cfg(feature = "mars")]
23pub mod mars;
24
25#[cfg(feature = "hifitime")]
26mod hifitime;
27
28#[cfg(feature = "chrono")]
29mod chrono;
30
31#[cfg(feature = "jiff")]
32mod jiff;
33
34use crate::{ATTOS_PER_SEC, Scale};
35use core::fmt;
36
37/// **The library's central time type.** A high-precision instant/duration with attosecond
38/// resolution.
39///
40/// **Fields:**
41///
42/// - pub attos: [`i128`] - total time in attoseconds since the reference epoch
43///   (2000-01-01 noon), as a signed integer. Negative values represent times
44///   before the epoch.
45/// - pub scale: [`Scale`] - the current time scale of the object.
46/// - pub target: [`Scale`] - a target time scale used by many output functions such as
47///   [`Dt::to_ymd`](../struct.Dt.html#method.to_ymd) and
48///   [`Dt::to_unix`](../struct.Dt.html#method.to_unix). The functions convert to the
49///   `target` time scale before producing an output.
50///
51/// **Notes:**
52///
53/// - In theory it supports a range of roughly ±5.39 trillion years but many of the to and
54///   from functions cap at i64 seconds, which can mean a range of ±292 billion years in practice.
55///   Additionally, when parsing dates with a timezone the Rust library `jiff` is used which has
56///   a limit of `-9999 - 9999` years.
57/// - Implements `Copy` and `Clone`. Optional derives for `serde` and `tsify` are available
58///   behind the corresponding features.
59/// - A wide range of math is available for this type, including basic calendar aware math and,
60///   with the `jiff-tz` feature enabled, timezone and DST aware math. **Behavior greatly
61///   differs between functions.**
62///
63/// ## Reference epoch and scales
64///
65/// - The librarys epoch for nearly all functionality such as the conversion functions is
66///   **2000-01-01 noon**. See also: [`Scale`](../enum.Scale.html).
67/// - Leap-second handling follows the chosen `Scale` (UTC, UtcSpice, UtcHist).
68///
69/// ## See also (non-exhaustive list)
70///
71/// ### From and to calendar dates
72///
73/// - [`Dt::from_ymd`](../struct.Dt.html#method.from_ymd)
74/// - [`Dt::to_ymd`](../struct.Dt.html#method.to_ymd)
75///
76/// ### From and to str and bytes
77///
78/// Some of these require the alloc feature, they're marked with *
79///
80/// - [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse)*
81/// - [`Dt::from_str_iso`](../struct.Dt.html#method.from_str_iso)
82/// - [`Dt::parse`](../struct.Dt.html#method.parse)
83/// - [`Dt::from_str`](../struct.Dt.html#method.from_str)
84/// - [`Dt::to_str`](../struct.Dt.html#method.to_str)*
85/// - [`Dt::to_str_in_offset`](../struct.Dt.html#method.to_str_in_offset)*
86/// - [`Dt::to_str_in_tz`](../struct.Dt.html#method.to_str_in_tz)*
87/// - [`Dt::to_str_iso8601`](../struct.Dt.html#method.to_str_iso8601)*
88/// - [`Dt::to_str_lite`](../struct.Dt.html#method.to_str_lite)
89/// - [`Dt::to_str_lite_in_offset`](../struct.Dt.html#method.to_str_lite_in_offset)
90/// - [`Dt::to_str_lite_in_tz`](../struct.Dt.html#method.to_str_lite_in_tz)
91///
92/// ### From and to julian dates
93///
94/// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
95/// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
96/// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
97/// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
98/// - [`Dt::ymd_to_jd`](../struct.Dt.html#method.ymd_to_jd)
99/// - [`Dt::jd_to_ymd`](../struct.Dt.html#method.jd_to_ymd)
100///
101/// ### Conversions, time scales etc.
102///
103/// - [`Dt::target`](../struct.Dt.html#method.target)
104/// - [`Dt::to`](../struct.Dt.html#method.to)
105/// - [`Dt::convert`](../struct.Dt.html#method.convert)
106/// - [`Dt::to_tai`](../struct.Dt.html#method.to_tai)
107/// - [`Dt::from_sec`](../struct.Dt.html#method.from_sec)
108/// - [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64)
109/// - [`Dt::from_attos`](../struct.Dt.html#method.from_attos)
110/// - [`Dt::convert_internal`](../struct.Dt.html#method.convert_internal)
111/// - [`Dt::to_unix`](../struct.Dt.html#method.to_unix)
112/// - [`Dt::to_ntp`](../struct.Dt.html#method.to_ntp)
113/// - [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow)
114///
115/// ### Conversions from and to types from other libraries
116///
117/// - [`Dt::to_hifitime_epoch`](../struct.Dt.html#method.to_hifitime_epoch)
118/// - [`Dt::to_jiff_timestamp`](../struct.Dt.html#method.to_jiff_timestamp)
119/// - [`Dt::to_chrono_datetime_utc`](../struct.Dt.html#method.to_chrono_datetime_utc)
120/// - [`Dt::from_hifitime_epoch`](../struct.Dt.html#method.from_hifitime_epoch)
121/// - [`Dt::from_jiff_timestamp`](../struct.Dt.html#method.from_jiff_timestamp)
122/// - [`Dt::from_chrono_datetime_utc`](../struct.Dt.html#method.from_chrono_datetime_utc)
123///
124/// ## Examples
125///
126/// ### Parsing a date
127///
128/// ```rust
129/// use deep_time::{Dt, Scale};
130///
131/// // uses impl FromStr but Dt::parse provides the same functionality
132/// let x: Dt = "2000-01-01 12:00:00".parse().unwrap();
133///
134/// let ymd = x.to_ymd();
135/// assert_eq!(ymd.yr(), 2000);
136/// assert_eq!(ymd.mo(), 1);
137/// assert_eq!(ymd.day(), 1);
138/// assert_eq!(ymd.hr(), 12);
139/// assert_eq!(ymd.min(), 0);
140/// assert_eq!(ymd.sec(), 0);
141/// assert_eq!(ymd.attos(), 0);
142/// ```
143///
144/// ### Outputting a date to string / bytes
145///
146/// ```rust
147/// # #[cfg(all(feature = "jiff-tz", feature = "parse"))]
148/// # {
149/// use deep_time::{Dt, Lang, Scale};
150///
151/// let x: Dt = "2000-01-01 12:00:00".parse().unwrap();
152///
153/// let s = x
154///  .to_str_in_tz("%A, %B %d, %Y %H:%M:%S %Q", "America/New_York", Lang::En)
155///  .unwrap();
156/// let b = x
157///  .to_str_lite_in_tz("%A, %B %d, %Y %H:%M:%S %Q", "America/New_York", Lang::En)
158///  .unwrap();
159///
160/// assert_eq!(s, "Saturday, January 01, 2000 07:00:00 America/New_York");
161/// assert_eq!(b.as_str(), "Saturday, January 01, 2000 07:00:00 America/New_York");
162/// # }
163/// ```
164///
165/// ### Creating a unix timestamp in milliseconds
166///
167/// ```rust
168/// use deep_time::{Dt, Scale};
169///
170/// // this fn converts from UTC and creates a TAI Dt
171/// let dt = Dt::from_ymd(2000, 1, 1, 12, 0, 0, 0, Scale::UTC);
172///
173/// // dt is internally TAI but has a UTC tag
174/// let unix_ms = dt.to_unix().to_ms();
175///
176/// // unix timestamp in ms for 2000-01-01 noon UTC
177/// assert_eq!(unix_ms, 946728000000);
178/// ```
179///
180/// ### Converting time scales
181///
182/// Many functions such as
183/// [`Dt::to_ymd`](../struct.Dt.html#method.to_ymd) will convert to
184/// `TAI` from the [`Dt`]s current `scale` then to the [`Dt`]s `target`
185/// [`Scale`] prior to producing an output.
186///
187/// So you don't necessarily have to convert time scales prior to using
188/// many of the output functions. You just have to change the `target`
189/// time scale.
190///
191/// #### Using the target field
192///
193/// ```rust
194/// use deep_time::{Dt, Lang, Scale};
195///
196/// // Leap seconds were added to the secounds count
197/// // This Dt has attos that are now on the TAI timescale
198/// let dt = Dt::from_ymd(2025, 1, 1, 0, 0, 0, 0, Scale::UTC);
199///
200/// // The internal target is currently UTC so we don't need to do
201/// // anything to output back to UTC and round trip
202/// let bytes = dt.to_str_lite("%d %m %Y %H:%M:%S", Lang::En).unwrap();
203///
204/// assert_eq!(bytes.as_str(), "01 01 2025 00:00:00");
205///
206/// // Perhaps we want to make a GPS timestamp out of our Dt
207/// // If we want it to be on the GPS time scale we have to set the
208/// // target prior to calling to_gps()
209/// let gps = dt.target(Scale::GPS).to_gps().to_sec_f();
210/// ```
211///
212/// #### Converting the internal attos to a new time scale
213///
214/// ```rust
215/// use deep_time::{Dt, Scale};
216///
217/// // this fn converts from UTC and creates a TAI Dt
218/// let dt = Dt::from_ymd(2000, 1, 1, 12, 0, 0, 0, Scale::UTC);
219///
220/// // to tdb
221/// let tdb = dt.to(Scale::TDB);
222///
223/// // then to tt, the current scale is TDB
224/// let tt = tdb.to(Scale::TT);
225///
226/// // then back to TAI
227/// let tai = tt.to(Scale::TAI);
228///
229/// // round trip equality
230/// assert_eq!(dt, tai);
231/// ```
232///
233/// ### Performing some basic calendar aware math
234///
235/// ```rust
236/// use deep_time::{Dt, Scale};
237///
238/// let x = Dt::from_ymd(2000, 2, 29, 0, 0, 0, 0, Scale::UTC).to_ymd();
239/// let x = x.add_yr(1);
240///
241/// assert_eq!(x.day(), 28);
242/// ```
243///
244/// ### Changing a dates format
245///
246/// ```rust
247/// use deep_time::{Dt, Lang, StrPTimeFmt};
248///
249/// let fmt = Dt::parse_fmt("%Y-%m-%dT%H:%M:%S").unwrap();
250///
251/// # #[cfg(feature = "alloc")]
252/// let s = fmt.to_str("2000-01-01T12:00:00", "%d %m %Y %H:%M:%S", false, false, false, Lang::En).unwrap();
253///
254/// # #[cfg(feature = "alloc")]
255/// assert_eq!(s, "01 01 2000 12:00:00", "expected: {}, got: {}", "01 01 2000 12:00:00", s);
256/// ```
257#[derive(Clone, Copy)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259#[cfg_attr(feature = "js", derive(tsify::Tsify))]
260pub struct Dt {
261    pub attos: i128,
262    pub scale: Scale,
263    pub target: Scale,
264}
265
266impl Dt {
267    /// Returns a new [`Dt`] with the `target` field set to the given
268    /// `t` arg.
269    #[inline(always)]
270    pub const fn target(&self, t: Scale) -> Dt {
271        Dt::new(self.attos, self.scale, t)
272    }
273
274    #[inline(always)]
275    pub(crate) const fn with(&self, s: Scale) -> Dt {
276        Dt::new(self.attos, s, self.target)
277    }
278}
279
280impl Default for Dt {
281    fn default() -> Dt {
282        Self::ZERO
283    }
284}
285
286impl fmt::Display for Dt {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        let total = self.to_attos();
289        let precision = f.precision().unwrap_or(9).min(18);
290
291        let is_negative = total < 0;
292        let abs_attos = if is_negative {
293            total.wrapping_neg() as u128
294        } else {
295            total as u128
296        };
297
298        if is_negative {
299            f.write_str("-")?;
300        } else if f.sign_plus() {
301            f.write_str("+")?;
302        }
303
304        let attos_per_sec = ATTOS_PER_SEC as u128;
305        let whole_seconds = abs_attos / attos_per_sec;
306        let fractional_attos = abs_attos % attos_per_sec;
307
308        // Integer seconds
309        write!(f, "{}", whole_seconds)?;
310
311        // Fractional part (only when requested *and* non-zero after truncation)
312        if precision > 0 && fractional_attos > 0 {
313            let scale = 10u128.pow(18 - precision as u32);
314            let frac_value = fractional_attos / scale;
315
316            if frac_value > 0 {
317                write!(f, ".{:0>width$}", frac_value, width = precision)?;
318            }
319        }
320
321        Ok(())
322    }
323}
324
325impl fmt::Debug for Dt {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        f.debug_struct("Dt")
328            .field("attos", &self.to_attos())
329            .field("scale", &self.scale)
330            .field("target", &self.target)
331            .finish()
332    }
333}