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}