Skip to main content

deep_time/dt/
constructors.rs

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