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