Skip to main content

deep_time/dt/
constructors.rs

1use crate::{
2    ATTOS_PER_FS, ATTOS_PER_MS, ATTOS_PER_NS, ATTOS_PER_PS, ATTOS_PER_SEC_I128, ATTOS_PER_US, Dt,
3    Real, SEC_PER_DAYI64, SEC_PER_DAYI128, SEC_PER_WEEK, Scale,
4    TAI_SECS_1970_MIDNIGHT_TO_2000_NOON,
5};
6
7impl Dt {
8    /// The library’s internal reference epoch: exactly **2000-01-01 12:00:00 TAI**.
9    ///
10    /// [`Dt::new(0)`].
11    pub const ZERO: Self = Self::new(0, Scale::TAI, Scale::TAI);
12
13    /// NTP epoch.
14    /// - 1900-01-01 00:00:00 UTC.
15    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
16    /// - -3_155_716_800_000_000_000_000_000_000 attoseconds
17    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
18    pub const NTP_EPOCH: Self =
19        Self::new(-3155716800000000000000000000i128, Scale::TAI, Scale::TAI);
20
21    /// UNIX epoch.
22    /// - 1970-01-01 00:00:00 TAI.
23    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
24    /// - -946_728_000_000_000_000_000_000_000 attoseconds
25    /// - Does not take into account historical UTC offsets from the "rubber time" era.
26    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
27    pub const UNIX_EPOCH: Self = Self::new(
28        -(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON as i128) * ATTOS_PER_SEC_I128,
29        Scale::TAI,
30        Scale::UTC,
31    );
32
33    /// TT/TCG/TCB/TDB epoch.
34    /// - 1977-01-01 00:00:00 TAI.
35    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
36    /// - -725_803_200_000_000_000_000_000_000 attoseconds
37    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
38    pub const TAI_1977_EPOCH: Self =
39        Self::new(-725803200000000000000000000i128, Scale::TAI, Scale::TAI);
40
41    /// Chandra X-ray Center (CXC) Time epoch.
42    /// - 1998-01-01 00:00:00 TT.
43    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
44    /// - -63_115_232_184_000_000_000_000_000_000 attoseconds
45    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
46    pub const CXC_EPOCH: Self = Self::new(-63115232184000000000000000i128, Scale::TAI, Scale::TT);
47
48    /// GPS/Galileo Experiment (GALEX) Time epoch.
49    /// - 1980-01-06 00:00:00 UTC.
50    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
51    /// - -630_763_181_000_000_000_000_000_000 attoseconds
52    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
53    pub const GPS_EPOCH: Self = Self::new(-630763181000000000000000000i128, Scale::TAI, Scale::GPS);
54
55    /// Galileo System Time (GST) epoch.
56    /// - 1999-08-22 00:00:00 GST.
57    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
58    /// - -11_447_981_000_000_000_000_000_000 attoseconds
59    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
60    pub const GALILEO_EPOCH: Self =
61        Self::new(-11447981000000000000000000i128, Scale::TAI, Scale::GST);
62
63    /// BeiDou Time (BDT) epoch.
64    /// - 2006-01-01 00:00:00 UTC.
65    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
66    /// - 189_345_633_000_000_000_000_000_000 attoseconds
67    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
68    pub const BDT_EPOCH: Self = Self::new(189345633000000000000000000i128, Scale::TAI, Scale::BDT);
69
70    /// CCSDS epoch (used in CCSDS time codes such as CUC).
71    /// - 1958-01-01 00:00:00 TAI.
72    /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
73    /// - -1_325_419_200_000_000_000_000_000_000 attoseconds
74    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
75    pub const CCSDS_EPOCH: Self = Self::new(
76        -1_325_419_200_000_000_000_000_000_000i128,
77        Scale::TAI,
78        Scale::TAI,
79    );
80
81    /// Maximum representable duration.
82    pub const MAX: Self = Self::new(i128::MAX, Scale::TAI, Scale::TAI);
83
84    /// Minimum (most negative) representable duration.
85    pub const MIN: Self = Self::new(i128::MIN, Scale::TAI, Scale::TAI);
86
87    pub const SEC_19: Self = Self::new(19i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
88    pub const SEC_33: Self = Self::new(33i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
89    pub const SEC_37: Self = Self::new(37i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
90    pub const ONE_DAY: Self = Self::new(
91        (SEC_PER_DAYI64 as i128) * ATTOS_PER_SEC_I128,
92        Scale::TAI,
93        Scale::TAI,
94    );
95
96    /// Creates a new `Dt` from a total number of attoseconds (signed i128).
97    #[inline(always)]
98    pub const fn new(attos: i128, scale: Scale, target: Scale) -> Dt {
99        Self {
100            attos,
101            scale,
102            target,
103        }
104    }
105
106    /// Creates a new [`Dt`] from a total number of attoseconds (signed i128) without
107    /// performing any time scale conversions.
108    ///
109    /// This is an easy way to create a duration.
110    #[inline(always)]
111    pub const fn span(attos: i128) -> Dt {
112        Dt::new(attos, Scale::TAI, Scale::TAI)
113    }
114
115    /// Creates a [`Dt`] from a floating-point number of seconds without performing
116    /// any time scale conversions.
117    ///
118    /// This is an easy way to create a duration.
119    #[inline(always)]
120    pub const fn span_f(sec_f: Real) -> Dt {
121        Self::from_sec_f_on(sec_f, Scale::TAI)
122    }
123
124    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
125    /// the given `scale`.
126    ///
127    /// - Requires a seconds and attoseconds count such that would be returned from the
128    ///   functions [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64) and
129    ///   [`Dt::to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
130    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
131    ///   the given `scale` arg.
132    /// - The `sec` should be from the epoch TAI 2000-01-01 12:00:00.
133    #[inline(always)]
134    pub(crate) fn from_sec_and_attos(sec: i64, attos: u64, scale: Scale) -> Dt {
135        if attos == 0 {
136            Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128, scale)
137        } else {
138            Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128 + (attos as i128), scale)
139        }
140    }
141
142    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
143    /// the given `scale`.
144    ///
145    /// - Requires a total attoseconds value.
146    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
147    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
148    ///   the given `scale` arg.
149    #[inline(always)]
150    pub const fn from_attos(attos: i128, current: Scale) -> Dt {
151        Dt::new(attos, current, current).to_tai()
152    }
153
154    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
155    /// the given `scale`.
156    ///
157    /// - Requires a total attoseconds value.
158    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
159    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
160    ///   the given `scale` arg.
161    #[inline(always)]
162    pub const fn from_attos_with_target(attos: i128, current: Scale, target: Scale) -> Dt {
163        Dt::new(attos, current, target).to_tai()
164    }
165
166    /// Creates a new [`Dt`] from a total number of seconds (signed i128) without
167    /// performing any time scale conversions.
168    #[inline(always)]
169    pub const fn from_tai_sec(sec: i128) -> Dt {
170        Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), Scale::TAI)
171    }
172
173    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
174    /// the given `scale`.
175    ///
176    /// - Requires a total seconds value.
177    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
178    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
179    ///   the given `scale` arg.
180    #[inline(always)]
181    pub const fn from_sec(sec: i128, scale: Scale) -> Dt {
182        Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), scale)
183    }
184
185    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
186    /// the given `scale`.
187    ///
188    /// - Requires a total milliseconds value.
189    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
190    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
191    ///   the given `scale` arg.
192    #[inline(always)]
193    pub const fn from_ms(ms: i128, scale: Scale) -> Dt {
194        let attos = ms.saturating_mul(ATTOS_PER_MS as i128);
195        Self::from_attos(attos, scale)
196    }
197
198    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
199    /// the given `scale`.
200    ///
201    /// - Requires a total microseconds value.
202    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
203    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
204    ///   the given `scale` arg.
205    #[inline(always)]
206    pub const fn from_us(us: i128, scale: Scale) -> Dt {
207        let attos = us.saturating_mul(ATTOS_PER_US as i128);
208        Self::from_attos(attos, scale)
209    }
210
211    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
212    /// the given `scale`.
213    ///
214    /// - Requires a total nanoseconds value.
215    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
216    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
217    ///   the given `scale` arg.
218    #[inline(always)]
219    pub const fn from_ns(ns: i128, scale: Scale) -> Dt {
220        let attos = ns.saturating_mul(ATTOS_PER_NS as i128);
221        Self::from_attos(attos, scale)
222    }
223
224    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
225    /// the given `scale`.
226    ///
227    /// - Requires a total picoseconds value.
228    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
229    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
230    ///   the given `scale` arg.
231    #[inline(always)]
232    pub const fn from_ps(ps: i128, scale: Scale) -> Dt {
233        let attos = ps.saturating_mul(ATTOS_PER_PS as i128);
234        Self::from_attos(attos, scale)
235    }
236
237    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
238    /// the given `scale`.
239    ///
240    /// - Requires a total femtoseconds value.
241    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
242    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
243    ///   the given `scale` arg.
244    #[inline(always)]
245    pub const fn from_fs(fs: i128, scale: Scale) -> Dt {
246        let attos = fs.saturating_mul(ATTOS_PER_FS as i128);
247        Self::from_attos(attos, scale)
248    }
249
250    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
251    /// the given `scale`.
252    ///
253    /// Convenience wrapper around
254    /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
255    #[inline(always)]
256    pub const fn from_min(m: i64, scale: Scale) -> Dt {
257        Self::from_sec((m as i128) * 60, scale)
258    }
259
260    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
261    /// the given `scale`.
262    ///
263    /// Convenience wrapper around
264    /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
265    #[inline(always)]
266    pub const fn from_hr(h: i64, scale: Scale) -> Dt {
267        Self::from_sec((h as i128) * 3600, scale)
268    }
269
270    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
271    /// the given `scale`.
272    ///
273    /// - Params are hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.
274    /// - All values are essentially optional (you can use 0 for ones you want to leave out).
275    /// - Negative values are handled.
276    /// - Uses saturating arithmetic.
277    pub const fn from_hms(
278        hr: i64,
279        min: i64,
280        sec: i64,
281        ms: i128,
282        us: i128,
283        ns: i128,
284        scale: Scale,
285    ) -> Dt {
286        // Combine hours/minutes/seconds with saturating arithmetic
287        let total_sec: i128 = (hr as i128)
288            .saturating_mul(3600)
289            .saturating_add((min as i128).saturating_mul(60))
290            .saturating_add(sec as i128);
291
292        // Combine sub-second parts (nanoseconds) with saturating arithmetic
293        let sub_ns: i128 = ms
294            .saturating_mul(1_000_000)
295            .saturating_add(us.saturating_mul(1_000))
296            .saturating_add(ns);
297
298        if sub_ns == 0 {
299            return Self::from_sec(total_sec, scale);
300        }
301
302        // Handle carry/borrow from sub-second component
303        let abs_ns: u128 = sub_ns.unsigned_abs();
304        let extra_sec: i128 = (abs_ns / 1_000_000_000) as i128;
305        let rem_ns: u64 = (abs_ns % 1_000_000_000) as u64;
306        let frac_attos: u128 = (rem_ns as u128) * (ATTOS_PER_NS as u128);
307
308        let attos = if sub_ns >= 0 {
309            total_sec
310                .saturating_add(extra_sec)
311                .saturating_mul(ATTOS_PER_SEC_I128)
312                .saturating_add(frac_attos as i128)
313        } else if frac_attos == 0 {
314            total_sec
315                .saturating_sub(extra_sec)
316                .saturating_mul(ATTOS_PER_SEC_I128)
317        } else {
318            total_sec
319                .saturating_sub(extra_sec)
320                .saturating_sub(1)
321                .saturating_mul(ATTOS_PER_SEC_I128)
322                .saturating_add(ATTOS_PER_SEC_I128 - frac_attos as i128)
323        };
324
325        Self::from_attos(attos, scale)
326    }
327
328    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
329    /// the given `scale`.
330    ///
331    /// - Convenience wrapper around
332    ///   [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
333    /// - Uses `86400` seconds per day in the calculation.
334    #[inline(always)]
335    pub const fn from_days(d: i64, scale: Scale) -> Dt {
336        Self::from_sec((d as i128).saturating_mul(SEC_PER_DAYI128), scale)
337    }
338
339    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
340    /// the given `scale`.
341    ///
342    /// - Convenience wrapper around
343    ///   [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
344    /// - Uses `604800` seconds per week in the calculation.
345    #[inline(always)]
346    pub const fn from_wk(wk: i64, scale: Scale) -> Dt {
347        Dt::from_sec((wk as i128).saturating_mul(SEC_PER_WEEK as i128), scale)
348    }
349
350    /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
351    /// the given `scale`.
352    ///
353    /// - Convenience wrapper around
354    ///   [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
355    /// - Uses `31_557_600` in the calculation.
356    #[inline(always)]
357    pub const fn from_yr(yr: i64, scale: Scale) -> Dt {
358        Dt::from_sec((yr as i128).saturating_mul(31_557_600), scale)
359    }
360
361    /// Returns a [`Dt`] that is this duration ago from the given scale.
362    #[inline(always)]
363    pub const fn ago(self, scale: Scale) -> Dt {
364        Dt::from_attos(0, scale).sub(self)
365    }
366
367    /// Returns a [`Dt`] that is this duration from now in the given scale.
368    #[inline(always)]
369    pub const fn from_now(self, scale: Scale) -> Dt {
370        Dt::from_attos(0, scale).add(self)
371    }
372
373    /// Returns the negation of this [`Dt`].
374    #[inline(always)]
375    pub const fn neg(self) -> Dt {
376        Dt::new(-self.attos, self.scale, self.target)
377    }
378
379    /// Returns the positive of this [`Dt`].
380    #[inline(always)]
381    pub const fn abs(self) -> Dt {
382        Dt::new(self.attos.saturating_abs(), self.scale, self.target)
383    }
384
385    /// Creates a [`Dt`] from a floating-point number of seconds.
386    #[inline(always)]
387    pub const fn from_sec_f(sec_f: Real, scale: Scale) -> Dt {
388        Self::from_sec_f_on(sec_f, scale)
389    }
390
391    /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
392    /// Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
393    /// Returns the rounded integer (round-to-nearest, ties away from zero).
394    pub const fn sec_f_to_total_attos(sec_f: Real) -> i128 {
395        if sec_f == 0.0 {
396            return 0;
397        }
398
399        let bits = sec_f.to_bits();
400        let is_negative = (bits >> 63) != 0;
401        let biased_exp = ((bits >> 52) & 0x7ff) as i32;
402        let mantissa = bits & 0x000f_ffff_ffff_ffff;
403
404        let (sig, exp) = if biased_exp == 0 {
405            if mantissa == 0 {
406                return 0;
407            }
408            (mantissa as u128, -1022i32 - 52)
409        } else {
410            let sig = ((1u64 << 52) | mantissa) as u128;
411            (sig, biased_exp - 1023 - 52)
412        };
413
414        const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
415        let product = sig * FIVE_POW_18;
416        let total_exp = exp + 18;
417
418        // Safe saturation / underflow guards (prevents invalid shifts >= 128)
419        if total_exp > 120 {
420            return if is_negative { i128::MIN } else { i128::MAX };
421        }
422        if total_exp < -97 {
423            return 0;
424        }
425
426        let abs_total = if total_exp >= 0 {
427            let shift = total_exp as u32;
428            if product > (u128::MAX >> shift) {
429                if is_negative { i128::MIN } else { i128::MAX }
430            } else {
431                let shifted = product << shift;
432                if shifted > i128::MAX as u128 {
433                    if is_negative { i128::MIN } else { i128::MAX }
434                } else {
435                    shifted as i128
436                }
437            }
438        } else {
439            let shift = (-total_exp) as u32;
440            let int_part = (product >> shift) as i128;
441
442            // Round to nearest, half away from zero (on the absolute value)
443            let mask = (1u128 << shift) - 1;
444            let rem = product & mask;
445            if rem > (mask >> 1) {
446                int_part + 1
447            } else {
448                int_part
449            }
450        };
451
452        if is_negative { -abs_total } else { abs_total }
453    }
454
455    /// Creates a [`Dt`] from a floating-point number of seconds.
456    /// - Assumes the value is on the given scale.
457    /// - Converts the values **to TAI**, the returned [`Dt`] is on
458    ///   the TAI time scale.
459    pub const fn from_sec_f_on(sec_f: Real, s: Scale) -> Dt {
460        if sec_f.is_nan() {
461            return Self::ZERO;
462        } else if sec_f.is_infinite() {
463            return if sec_f.is_sign_positive() {
464                Self::MAX
465            } else {
466                Self::MIN
467            };
468        }
469
470        let total_attos = Self::sec_f_to_total_attos(sec_f);
471
472        Self::from_attos(total_attos, s)
473    }
474
475    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
476    ///
477    /// This method is only available when the `std` feature is enabled and the target
478    /// is not WASM with the `js` feature.
479    #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
480    pub fn now() -> Dt {
481        let now = std::time::SystemTime::now();
482
483        let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
484            Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
485            Err(e) => {
486                let dur = e.duration();
487                (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
488            }
489        };
490
491        Dt::from_diff_and_scale(
492            Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
493            Dt::UNIX_EPOCH,
494            false,
495        )
496        .add(Dt::from_ns(nanos as i128, Scale::TAI))
497    }
498
499    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
500    /// (browser WASM version using JavaScript’s `Date.now()`).
501    #[cfg(all(target_arch = "wasm32", feature = "js"))]
502    pub fn now() -> Dt {
503        let ms: f64 = js_sys::Date::now();
504        let secs = (ms / 1000.0).floor() as i128;
505        let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
506        Dt::from_diff_and_scale(
507            Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
508            Dt::UNIX_EPOCH,
509            false,
510        )
511        .add(Dt::from_ns(nanos as i128, Scale::TAI))
512    }
513}