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