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