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