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 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;
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 sec: i64` — whole seconds (signed).
43/// - `pub attos: u64` — fractional seconds in attoseconds (`0 ≤ attos < 10¹⁸`). These always
44///   push towards the positive.
45///
46/// **Notes:**
47///
48/// - Supports a range of roughly ±292 billion years.
49/// - Implements `Copy` and `Clone`. Optional derives for `serde` and `tsify` are available
50///   behind the corresponding features.
51/// - Does **not** store a time scale internally. The scale is always an explicit parameter
52///   of conversion and construction methods.
53/// - A wide range of math is available for this type, but it's not calendar aware, for basic
54///   calendar aware math use the [`YmdHms`] type.
55///
56/// ## Reference epoch and scales
57///
58/// - The librarys epoch for nearly all functionality such as the conversion functions is
59///   **2000-01-01 noon**. See also: [`Scale`](../enum.Scale.html).
60/// - When using the conversion functions
61///   and [`Dt::from`](../struct.Dt.html#method.from) etc. the epoch is
62/// - Leap-second handling follows the chosen `Scale` (UTC, UTCSpice, UTCSofa).
63///
64/// ## See also (non-exhaustive list)
65///
66/// ### From and to calendar dates
67///
68/// - [`Dt::from_ymd`](../struct.Dt.html#method.from_ymd)
69/// - [`Dt::from_ymdhms`](../struct.Dt.html#method.from_ymdhms)
70/// - [`Dt::from_ymdhms_on`](../struct.Dt.html#method.from_ymdhms_on)
71/// - [`Dt::to_ymdhms`](../struct.Dt.html#method.to_ymdhms)
72/// - [`Dt::to_ymdhms_on`](../struct.Dt.html#method.to_ymdhms_on)
73/// - [`Dt::to_ymdhms_rich_on`](../struct.Dt.html#method.to_ymdhms_rich_on)
74///
75/// ### From and to str and bytes
76///
77/// Some of these require the alloc feature, they're marked with *
78///
79/// - [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse)*
80/// - [`Dt::from_str_ccsds`](../struct.Dt.html#method.from_str_ccsds)
81/// - [`Dt::parse`](../struct.Dt.html#method.parse)
82/// - [`Dt::from_str`](../struct.Dt.html#method.from_str)
83/// - [`Dt::to_str`](../struct.Dt.html#method.to_str)*
84/// - [`Dt::to_str_with_offset`](../struct.Dt.html#method.to_str_with_offset)*
85/// - [`Dt::to_str_with_tz`](../struct.Dt.html#method.to_str_with_tz)*
86/// - [`Dt::to_str_iso8601`](../struct.Dt.html#method.to_str_iso8601)*
87/// - [`Dt::to_str_bin`](../struct.Dt.html#method.to_str_bin)
88/// - [`Dt::to_str_bin_with_offset`](../struct.Dt.html#method.to_str_bin_with_offset)
89/// - [`Dt::to_str_bin_with_tz`](../struct.Dt.html#method.to_str_bin_with_tz)
90///
91/// ### From and to julian dates
92///
93/// - [`Dt::from_jd_f`](../struct.Dt.html#method.from_jd_f)
94/// - [`Dt::from_mjd_f`](../struct.Dt.html#method.from_mjd_f)
95/// - [`Dt::to_jd_f`](../struct.Dt.html#method.to_jd_f)
96/// - [`Dt::to_mjd_f`](../struct.Dt.html#method.to_mjd_f)
97/// - [`Dt::ymd_to_jd`](../struct.Dt.html#method.ymd_to_jd)
98/// - [`Dt::jd_to_ymd`](../struct.Dt.html#method.jd_to_ymd)
99///
100/// ### Conversions, time scales etc.
101///
102/// - [`Dt::from`](../struct.Dt.html#method.from)
103/// - [`Dt::to`](../struct.Dt.html#method.to)
104/// - [`Dt::to_unix`](../struct.Dt.html#method.to_unix)
105/// - [`Dt::to_ntp`](../struct.Dt.html#method.to_ntp)
106/// - [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow)
107///
108/// ### Conversions from and to types from other libraries
109///
110/// - [`Dt::to_hifitime_epoch`](../struct.Dt.html#method.to_hifitime_epoch)
111/// - [`Dt::to_jiff_timestamp`](../struct.Dt.html#method.to_jiff_timestamp)
112/// - [`Dt::to_chrono_datetime_utc`](../struct.Dt.html#method.to_chrono_datetime_utc)
113/// - [`Dt::from_hifitime_epoch`](../struct.Dt.html#method.from_hifitime_epoch)
114/// - [`Dt::from_jiff_timestamp`](../struct.Dt.html#method.from_jiff_timestamp)
115/// - [`Dt::from_chrono_datetime_utc`](../struct.Dt.html#method.from_chrono_datetime_utc)
116///
117/// ## Examples
118///
119/// ### Parsing a date
120///
121/// ```
122/// use deep_time::{Dt, Scale};
123///
124/// // uses impl FromStr but Dt::parse provides the same functionality
125/// let x: Dt = "2000-01-01 12:00:00".parse().unwrap();
126///
127/// let ymd = x.to_ymdhms(Scale::TAI);
128/// assert_eq!(ymd.yr(), 2000);
129/// assert_eq!(ymd.mo(), 1);
130/// assert_eq!(ymd.day(), 1);
131/// assert_eq!(ymd.hr(), 12);
132/// assert_eq!(ymd.min(), 0);
133/// assert_eq!(ymd.sec(), 0);
134/// assert_eq!(ymd.attos(), 0);
135/// ```
136///
137/// ### Outputting a date to string / bytes
138///
139/// ```
140/// # #[cfg(all(feature = "tz", feature = "parse"))]
141/// # {
142/// use deep_time::{Dt, Scale};
143///
144/// let x: Dt = "2000-01-01 12:00:00".parse().unwrap();
145///
146/// let s = x
147///  .to_str_with_tz(Scale::TAI, "%A, %B %d, %Y %H:%M:%S %Q", "America/New_York")
148///  .unwrap();
149/// let b = x
150///  .to_str_bin_with_tz(Scale::TAI, "%A, %B %d, %Y %H:%M:%S %Q", "America/New_York")
151///  .unwrap();
152///
153/// assert_eq!(s, "Saturday, January 01, 2000 07:00:00 America/New_York");
154/// assert_eq!(b.as_str().unwrap(), "Saturday, January 01, 2000 07:00:00 America/New_York");
155/// # }
156/// ```
157///
158/// ### Creating a unix timestamp in milliseconds
159///
160/// ```
161/// use deep_time::{Dt, Scale};
162///
163/// // this fn converts from UTC and creates a TAI Dt
164/// let dt = Dt::from_ymdhms(2000, 1, 1, 12, 0, 0, 0);
165///
166/// // dt is now TAI so the current Scale is TAI, it was originally UTC though
167/// let unix_ms = dt.to_unix(Scale::TAI, Scale::UTC).to_ms();
168///
169/// // unix timestamp in ms for 2000-01-01 noon UTC
170/// assert_eq!(unix_ms, 946728000000);
171/// ```
172///
173/// ### Converting time scales
174///
175/// ```
176/// use deep_time::{Dt, Scale};
177///
178/// // this fn converts from UTC and creates a TAI Dt
179/// let dt = Dt::from_ymdhms(2000, 1, 1, 12, 0, 0, 0);
180///
181/// // to tdb
182/// let tdb = dt.to(Scale::TAI, Scale::TDB);
183///
184/// // then to tt, the current scale is TDB
185/// let tt = tdb.to(Scale::TDB, Scale::TT);
186///
187/// // then back to TAI
188/// let tai = tt.to(Scale::TT, Scale::TAI);
189///
190/// // round trip equality
191/// assert_eq!(dt, tai);
192/// ```
193///
194/// ### Performing some basic calendar aware math
195///
196/// ```
197/// use deep_time::{Dt, Scale};
198///
199/// let x = Dt::from_ymd(2000, 2, 29).to_ymdhms(Scale::TAI);
200/// let x = x.add_yr(1);
201///
202/// assert_eq!(x.day(), 28);
203/// ```
204///
205/// ### Changing a dates format
206///
207/// ```
208/// use deep_time::{Dt, StrPTimeFmt};
209///
210/// let fmt = Dt::parse_fmt("%Y-%m-%dT%H:%M:%S").unwrap();
211///
212/// # #[cfg(feature = "alloc")]
213/// let s = fmt.to_str("2000-01-01T12:00:00", "%d %m %Y %H:%M:%S", false, false, false).unwrap();
214/// ```
215#[derive(Clone, Copy)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217#[cfg_attr(feature = "js", derive(tsify::Tsify))]
218pub struct Dt {
219    pub sec: i64,
220    pub attos: u64,
221}
222
223impl Dt {
224    /// Normalizes the representation so that the attosecond part lies in the range `[0, ATTOS_PER_SEC)`.
225    #[inline]
226    pub const fn carry_attos_mut(&mut self) -> &mut Self {
227        if self.attos >= ATTOS_PER_SEC {
228            self.sec = self.sec.saturating_add((self.attos / ATTOS_PER_SEC) as i64);
229            self.attos %= ATTOS_PER_SEC;
230        }
231        self
232    }
233
234    /// Normalizes the representation so that the attosecond part lies in the range `[0, ATTOS_PER_SEC)`.
235    #[inline]
236    pub const fn carry_attos(&self) -> Self {
237        if self.attos < ATTOS_PER_SEC {
238            return *self;
239        }
240        Self {
241            sec: self.sec.saturating_add((self.attos / ATTOS_PER_SEC) as i64),
242            attos: self.attos % ATTOS_PER_SEC,
243        }
244    }
245}
246
247impl Default for Dt {
248    fn default() -> Self {
249        Self::ZERO
250    }
251}
252
253impl fmt::Display for Dt {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        let sec = self.sec;
256        let attos = self.attos;
257
258        // Default to nanosecond precision (9 digits) — most useful for everyday use
259        let precision = f.precision().unwrap_or(9);
260
261        // Respect the `+` sign when the user writes {:+}
262        if f.sign_plus() && sec >= 0 {
263            write!(f, "+")?;
264        }
265
266        write!(f, "{}", sec)?;
267
268        if precision > 0 {
269            let prec = precision.min(18);
270            let scale = 10u64.pow(18 - prec as u32);
271            let value = attos / scale;
272            write!(f, ".{:0>width$}", value, width = prec)?;
273        }
274
275        Ok(())
276    }
277}
278
279impl fmt::Debug for Dt {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.debug_struct("Dt")
282            .field("sec", &self.sec)
283            .field("attos", &self.attos)
284            .finish()
285    }
286}