Skip to main content

deep_time/dt/
constructors.rs

1use crate::{
2    ATTOS_PER_DAY, ATTOS_PER_FS_I128, ATTOS_PER_HOUR, ATTOS_PER_MIN, ATTOS_PER_MS_I128,
3    ATTOS_PER_NS_I128, ATTOS_PER_PS_I128, ATTOS_PER_SEC_I128, ATTOS_PER_US_I128, Dt, Real,
4    SEC_PER_DAY_I64, SEC_PER_WEEK, Scale, 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_DAY_I64 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        Dt {
135            attos,
136            scale,
137            target,
138        }
139    }
140
141    /// Low level constructor from total attoseconds since a given epoch.
142    ///
143    /// Simply adds the total attoseconds to the epoch. Does not perform
144    /// any time scale conversions.
145    ///
146    /// The returned [`Dt`] copies the epoch's `scale` and `target` fields.
147    ///
148    /// ## Examples
149    ///
150    /// ```rust
151    /// use deep_time::{Dt, Scale};
152    ///
153    /// // A leap second from the middle of the table (36 leap seconds accumulated)
154    /// let original = Dt::from_ymd(2015, 6, 30, Scale::UTC, 23, 59, 60, 123_456_789_000_000_000);
155    ///
156    /// // Round-trip through canonical attoseconds
157    /// let canon = original.to_diff_raw(Dt::UNIX_EPOCH).to_attos();
158    /// let roundtrip1 = Dt::from_diff_raw(canon, Dt::UNIX_EPOCH);
159    ///
160    /// assert_eq!(original, roundtrip1, "Canonical round-trip failed");
161    /// ```
162    #[inline]
163    pub const fn from_diff_raw(attos: i128, epoch: Dt) -> Dt {
164        epoch.add(Dt::new(attos, epoch.scale, epoch.target))
165    }
166
167    /// Builds a [`Dt`] holding the given whole seconds and sub-second remainder.
168    ///
169    /// The remainder is in **attoseconds**, not seconds. Pairs with
170    /// [`to_sec64`](Self::to_sec64) + [`to_sec_frac`](Self::to_sec_frac).
171    ///
172    /// Does **not** perform any time scale conversions.
173    ///
174    /// ## Parameters
175    ///
176    /// - `sec` — whole seconds (truncating / signed-remainder split).
177    /// - `attos` — fractional part of that split, in attoseconds.
178    ///   For `1.3` s: `sec = 1`, `attos = 300_000_000_000_000_000`.
179    ///   For `-1.3` s: `sec = -1`, `attos = -300_000_000_000_000_000`.
180    ///   For `-0.5` s: `sec = 0`, `attos = -500_000_000_000_000_000`.
181    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
182    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
183    ///
184    /// ## Examples
185    ///
186    /// ```rust
187    /// use deep_time::{Dt, Scale, dt};
188    ///
189    /// let dt = dt!(1_300_000_000_000_000_000);
190    /// assert_eq!(
191    ///     Dt::from_sec_and_frac(1, 300_000_000_000_000_000, Scale::TAI, Scale::TAI),
192    ///     dt,
193    /// );
194    ///
195    /// let dt = dt!(-1_300_000_000_000_000_000);
196    /// assert_eq!(
197    ///     Dt::from_sec_and_frac(-1, -300_000_000_000_000_000, Scale::TAI, Scale::TAI),
198    ///     dt,
199    /// );
200    ///
201    /// let dt = dt!(-500_000_000_000_000_000);
202    /// assert_eq!(
203    ///     Dt::from_sec_and_frac(0, -500_000_000_000_000_000, Scale::TAI, Scale::TAI),
204    ///     dt,
205    /// );
206    /// ```
207    #[inline(always)]
208    pub const fn from_sec_and_frac(sec: i128, attos: i128, on: Scale, target: Scale) -> Dt {
209        Dt::new(
210            sec.saturating_mul(ATTOS_PER_SEC_I128).saturating_add(attos),
211            on,
212            target,
213        )
214    }
215
216    /// Builds a [`Dt`] holding the given whole seconds.
217    ///
218    /// Does **not** perform any time scale conversions. The `sec` count is stored
219    /// as-is (converted only from seconds to attoseconds); its meaning depends on
220    /// how you use the value afterward (for example as a library-epoch offset, a
221    /// Unix offset passed to [`from_unix`](Self::from_unix), a duration, etc.).
222    ///
223    /// ## Parameters
224    ///
225    /// - `sec` — whole seconds count to store.
226    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
227    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
228    #[inline(always)]
229    pub const fn from_sec(sec: i128, on: Scale, target: Scale) -> Dt {
230        Dt::new(sec.saturating_mul(ATTOS_PER_SEC_I128), on, target)
231    }
232
233    /// Builds a [`Dt`] holding the given whole milliseconds and sub-millisecond remainder.
234    ///
235    /// The remainder is in **attoseconds**, not milliseconds. Pairs with
236    /// [`to_ms`](../struct.Dt.html#method.to_ms).
237    ///
238    /// Does **not** perform any time scale conversions.
239    ///
240    /// ## Parameters
241    ///
242    /// - `ms` — whole milliseconds (truncating / signed-remainder split).
243    /// - `frac_attos` — fractional part of that split, in attoseconds.
244    ///   For `1.3` ms: `ms = 1`, `frac_attos` = 0.3 ms in attoseconds.
245    ///   For `-1.3` ms: `ms = -1`, `frac_attos` negative.
246    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
247    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
248    #[inline(always)]
249    pub const fn from_ms(ms: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
250        let attos = Dt::unit_and_signed_attos_to_attos(ms, frac_attos, ATTOS_PER_MS_I128);
251        Dt::new(attos, on, target)
252    }
253
254    /// Builds a [`Dt`] holding the given whole microseconds and sub-microsecond remainder.
255    ///
256    /// The remainder is in **attoseconds**, not microseconds. Pairs with
257    /// [`to_us`](../struct.Dt.html#method.to_us).
258    ///
259    /// Does **not** perform any time scale conversions.
260    ///
261    /// ## Parameters
262    ///
263    /// - `us` — whole microseconds (truncating / signed-remainder split).
264    /// - `frac_attos` — fractional part of that split, in attoseconds.
265    ///   For `1.3` µs: `us = 1`, `frac_attos` = 0.3 µs in attoseconds.
266    ///   For `-1.3` µs: `us = -1`, `frac_attos` negative.
267    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
268    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
269    #[inline(always)]
270    pub const fn from_us(us: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
271        let attos = Dt::unit_and_signed_attos_to_attos(us, frac_attos, ATTOS_PER_US_I128);
272        Dt::new(attos, on, target)
273    }
274
275    /// Builds a [`Dt`] holding the given whole nanoseconds and sub-nanosecond remainder.
276    ///
277    /// The remainder is in **attoseconds**, not nanoseconds. Pairs with
278    /// [`to_ns`](../struct.Dt.html#method.to_ns).
279    ///
280    /// Does **not** perform any time scale conversions.
281    ///
282    /// ## Parameters
283    ///
284    /// - `ns` — whole nanoseconds (truncating / signed-remainder split).
285    /// - `frac_attos` — fractional part of that split, in attoseconds.
286    ///   For `1.3` ns: `ns = 1`, `frac_attos` = 0.3 ns in attoseconds.
287    ///   For `-1.3` ns: `ns = -1`, `frac_attos` negative.
288    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
289    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
290    #[inline(always)]
291    pub const fn from_ns(ns: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
292        let attos = Dt::unit_and_signed_attos_to_attos(ns, frac_attos, ATTOS_PER_NS_I128);
293        Dt::new(attos, on, target)
294    }
295
296    /// Builds a [`Dt`] holding the given whole picoseconds and sub-picosecond remainder.
297    ///
298    /// The remainder is in **attoseconds**, not picoseconds. Pairs with
299    /// [`to_ps`](../struct.Dt.html#method.to_ps).
300    ///
301    /// Does **not** perform any time scale conversions.
302    ///
303    /// ## Parameters
304    ///
305    /// - `ps` — whole picoseconds (truncating / signed-remainder split).
306    /// - `frac_attos` — fractional part of that split, in attoseconds.
307    ///   For `1.3` ps: `ps = 1`, `frac_attos` = 0.3 ps in attoseconds.
308    ///   For `-1.3` ps: `ps = -1`, `frac_attos` negative.
309    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
310    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
311    #[inline(always)]
312    pub const fn from_ps(ps: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
313        let attos = Dt::unit_and_signed_attos_to_attos(ps, frac_attos, ATTOS_PER_PS_I128);
314        Dt::new(attos, on, target)
315    }
316
317    /// Builds a [`Dt`] holding the given whole femtoseconds and sub-femtosecond remainder.
318    ///
319    /// The remainder is in **attoseconds**, not femtoseconds. Pairs with
320    /// [`to_fs`](../struct.Dt.html#method.to_fs).
321    ///
322    /// Does **not** perform any time scale conversions.
323    ///
324    /// ## Parameters
325    ///
326    /// - `fs` — whole femtoseconds (truncating / signed-remainder split).
327    /// - `frac_attos` — fractional part of that split, in attoseconds.
328    ///   For `1.3` fs: `fs = 1`, `frac_attos` = 0.3 fs in attoseconds.
329    ///   For `-1.3` fs: `fs = -1`, `frac_attos` negative.
330    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
331    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
332    #[inline(always)]
333    pub const fn from_fs(fs: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
334        let attos = Dt::unit_and_signed_attos_to_attos(fs, frac_attos, ATTOS_PER_FS_I128);
335        Dt::new(attos, on, target)
336    }
337
338    /// Builds a [`Dt`] holding the given whole minutes and sub-minute remainder.
339    ///
340    /// The remainder is in **attoseconds**, not minutes.
341    ///
342    /// Does **not** perform any time scale conversions.
343    ///
344    /// ## Parameters
345    ///
346    /// - `n` — whole minutes (truncating / signed-remainder split).
347    /// - `frac_attos` — fractional part of that split, in attoseconds.
348    ///   For `1.5` min: `n = 1`, `frac_attos` = 0.5 min in attoseconds.
349    ///   For `-1.5` min: `n = -1`, `frac_attos` negative.
350    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
351    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
352    #[inline(always)]
353    pub const fn from_mins(n: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
354        let attos = Dt::unit_and_signed_attos_to_attos(n, frac_attos, ATTOS_PER_MIN);
355        Dt::new(attos, on, target)
356    }
357
358    /// Builds a [`Dt`] holding the given whole hours and sub-hour remainder.
359    ///
360    /// The remainder is in **attoseconds**, not hours.
361    ///
362    /// Does **not** perform any time scale conversions.
363    ///
364    /// ## Parameters
365    ///
366    /// - `n` — whole hours (truncating / signed-remainder split).
367    /// - `frac_attos` — fractional part of that split, in attoseconds.
368    ///   For `1.5` h: `n = 1`, `frac_attos` = 0.5 h in attoseconds.
369    ///   For `-1.5` h: `n = -1`, `frac_attos` negative.
370    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
371    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
372    #[inline(always)]
373    pub const fn from_hours(n: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
374        let attos = Dt::unit_and_signed_attos_to_attos(n, frac_attos, ATTOS_PER_HOUR);
375        Dt::new(attos, on, target)
376    }
377
378    /// Builds a [`Dt`] holding the given whole days and sub-day remainder.
379    ///
380    /// The remainder is in **attoseconds**, not days. Uses `86400` seconds per day.
381    ///
382    /// Does **not** perform any time scale conversions.
383    ///
384    /// ## Parameters
385    ///
386    /// - `d` — whole days (truncating / signed-remainder split).
387    /// - `frac_attos` — fractional part of that split, in attoseconds.
388    ///   For `1.25` d: `d = 1`, `frac_attos` = 0.25 d in attoseconds.
389    ///   For `-1.25` d: `d = -1`, `frac_attos` negative.
390    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
391    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
392    #[inline(always)]
393    pub const fn from_days(d: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
394        let attos = Dt::unit_and_signed_attos_to_attos(d, frac_attos, ATTOS_PER_DAY);
395        Dt::new(attos, on, target)
396    }
397
398    /// Builds a [`Dt`] holding the given number of weeks (`604800` seconds each).
399    ///
400    /// Does **not** perform any time scale conversions.
401    ///
402    /// ## Parameters
403    ///
404    /// - `n` — whole weeks.
405    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
406    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
407    #[inline(always)]
408    pub const fn from_weeks(n: i128, on: Scale, target: Scale) -> Dt {
409        Dt::new(
410            n.saturating_mul(SEC_PER_WEEK as i128)
411                .saturating_mul(ATTOS_PER_SEC_I128),
412            on,
413            target,
414        )
415    }
416
417    /// Builds a [`Dt`] holding the given number of Julian years (`31_557_600` seconds each).
418    ///
419    /// Does **not** perform any time scale conversions.
420    ///
421    /// ## Parameters
422    ///
423    /// - `n` — whole years.
424    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
425    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
426    #[inline(always)]
427    pub const fn from_years(n: i128, on: Scale, target: Scale) -> Dt {
428        Dt::new(
429            n.saturating_mul(31_557_600)
430                .saturating_mul(ATTOS_PER_SEC_I128),
431            on,
432            target,
433        )
434    }
435
436    /// Returns an instant that is this duration **before** zero attoseconds on `scale`.
437    ///
438    /// Zero attoseconds is the library epoch **2000-01-01 12:00:00** (see
439    /// [`Dt::ZERO`](../struct.Dt.html#associatedconstant.ZERO)).
440    ///
441    /// This method does **not** read the system clock.
442    ///
443    /// For wall-clock “N units ago”, use [`Dt::ago`](../struct.Dt.html#method.ago)
444    /// (requires `std`, or WASM with `js`).
445    ///
446    /// ## Examples
447    ///
448    /// ```rust
449    /// use deep_time::{Dt, Scale, TimeTraits};
450    ///
451    /// let t = 5.sec().before_zero(Scale::TAI);
452    /// assert_eq!(t, Dt::ZERO.sub(5.sec()));
453    /// assert_eq!(t.to_sec(), -5);
454    /// ```
455    ///
456    /// ## See also
457    ///
458    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
459    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
460    #[inline(always)]
461    pub const fn before_zero(self, scale: Scale) -> Dt {
462        Dt::new(0, scale, scale).to_tai().sub(self)
463    }
464
465    /// Returns the negation of this [`Dt`].
466    #[inline(always)]
467    pub const fn neg(self) -> Dt {
468        Dt::new(-self.attos, self.scale, self.target)
469    }
470
471    /// Returns the positive of this [`Dt`].
472    #[inline(always)]
473    pub const fn abs(self) -> Dt {
474        Dt::new(self.attos.saturating_abs(), self.scale, self.target)
475    }
476
477    /// Builds a [`Dt`] holding the given floating-point seconds count.
478    ///
479    /// Does **not** perform any time scale conversions. The `sec` value is
480    /// stored as attoseconds only; its meaning depends on how you use the
481    /// result afterward.
482    ///
483    /// ## Parameters
484    ///
485    /// - `sec` — seconds count to store (`NaN` → zero attoseconds;
486    ///   `±∞` → [`i128::MAX`] / [`i128::MIN`]).
487    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
488    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
489    ///
490    /// ## Examples
491    ///
492    /// ```rust
493    /// use deep_time::{Dt, Scale};
494    ///
495    /// let seconds = 5.5;
496    /// let duration = Dt::from_sec_f(seconds, Scale::TAI, Scale::TAI);
497    ///
498    /// assert_eq!(duration.to_sec_f(), seconds);
499    /// ```
500    #[inline]
501    pub const fn from_sec_f(sec: Real, on: Scale, target: Scale) -> Dt {
502        if sec.is_nan() {
503            return Self::new(0, on, target);
504        } else if sec.is_infinite() {
505            return if sec.is_sign_positive() {
506                Self::new(i128::MAX, on, target)
507            } else {
508                Self::new(i128::MIN, on, target)
509            };
510        }
511        Dt::new(Self::sec_f_to_attos(sec), on, target)
512    }
513
514    /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
515    ///
516    /// - Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
517    /// - Returns the rounded integer (round-to-nearest, ties away from zero).
518    pub const fn sec_f_to_attos(sec: Real) -> i128 {
519        if sec == 0.0 {
520            return 0;
521        }
522
523        let bits = sec.to_bits();
524        let is_negative = (bits >> 63) != 0;
525        let biased_exp = ((bits >> 52) & 0x7ff) as i32;
526        let mantissa = bits & 0x000f_ffff_ffff_ffff;
527
528        let (sig, exp) = if biased_exp == 0 {
529            if mantissa == 0 {
530                return 0;
531            }
532            (mantissa as u128, -1022i32 - 52)
533        } else {
534            let sig = ((1u64 << 52) | mantissa) as u128;
535            (sig, biased_exp - 1023 - 52)
536        };
537
538        const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
539        let product = sig * FIVE_POW_18;
540        let total_exp = exp + 18;
541
542        // Safe saturation / underflow guards (prevents invalid shifts >= 128)
543        if total_exp > 120 {
544            return if is_negative { i128::MIN } else { i128::MAX };
545        }
546        if total_exp < -97 {
547            return 0;
548        }
549
550        let abs_total = if total_exp >= 0 {
551            let shift = total_exp as u32;
552            if product > (u128::MAX >> shift) {
553                if is_negative { i128::MIN } else { i128::MAX }
554            } else {
555                let shifted = product << shift;
556                if shifted > i128::MAX as u128 {
557                    if is_negative { i128::MIN } else { i128::MAX }
558                } else {
559                    shifted as i128
560                }
561            }
562        } else {
563            let shift = (-total_exp) as u32;
564            let int_part = (product >> shift) as i128;
565
566            // Round to nearest, half away from zero (on the absolute value)
567            let mask = (1u128 << shift) - 1;
568            let rem = product & mask;
569            if rem > (mask >> 1) {
570                int_part + 1
571            } else {
572                int_part
573            }
574        };
575
576        if is_negative { -abs_total } else { abs_total }
577    }
578
579    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
580    ///
581    /// This method is only available when the `std` feature is enabled and the target
582    /// is not WASM with the `js` feature.
583    #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
584    pub fn now() -> Dt {
585        let now = std::time::SystemTime::now();
586
587        let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
588            Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
589            Err(e) => {
590                let dur = e.duration();
591                (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
592            }
593        };
594
595        Dt::from_diff_and_scale(
596            Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
597            Dt::UNIX_EPOCH,
598            false,
599        )
600        .add(Dt::from_ns(nanos as i128, 0, Scale::TAI, Scale::TAI))
601    }
602
603    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
604    /// (browser WASM version using JavaScript’s `Date.now()`).
605    #[cfg(all(target_arch = "wasm32", feature = "js"))]
606    pub fn now() -> Dt {
607        let ms: f64 = js_sys::Date::now();
608        let secs = (ms / 1000.0).floor() as i128;
609        let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
610        Dt::from_diff_and_scale(
611            Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
612            Dt::UNIX_EPOCH,
613            false,
614        )
615        .add(Dt::from_ns(nanos as i128, 0, Scale::TAI, Scale::TAI))
616    }
617
618    /// Returns an instant that is this duration **before** the current system time.
619    ///
620    /// Subtracts `self` from [`Dt::now`](../struct.Dt.html#method.now). Available under
621    /// the same conditions as that method: the `std` feature (non-WASM-js), or WASM with
622    /// the `js` feature.
623    ///
624    /// For a `const` offset from the library epoch (no system clock), use
625    /// [`Dt::before_zero`](../struct.Dt.html#method.before_zero).
626    ///
627    /// ## Examples
628    ///
629    /// ```rust
630    /// # #[cfg(feature = "std")]
631    /// # {
632    /// use deep_time::{Dt, TimeTraits};
633    ///
634    /// // ~3 days in the past relative to the system clock
635    /// let past = 3.days().ago();
636    /// assert!(past < Dt::now());
637    /// # }
638    /// ```
639    ///
640    /// ## See also
641    ///
642    /// - [`Dt::from_now`](../struct.Dt.html#method.from_now)
643    /// - [`Dt::before_zero`](../struct.Dt.html#method.before_zero)
644    /// - [`Dt::now`](../struct.Dt.html#method.now)
645    #[cfg(any(
646        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
647        all(target_arch = "wasm32", feature = "js"),
648    ))]
649    #[inline]
650    pub fn ago(self) -> Dt {
651        Dt::now().sub(self)
652    }
653
654    /// Returns an instant that is this duration **after** the current system time.
655    ///
656    /// Adds `self` to [`Dt::now`](../struct.Dt.html#method.now). Available under the same
657    /// conditions as that method: the `std` feature (non-WASM-js), or WASM with the `js`
658    /// feature.
659    ///
660    /// For a `const` offset from the library epoch (no system clock), use
661    /// [`Dt::after_zero`](../struct.Dt.html#method.after_zero).
662    ///
663    /// ## Examples
664    ///
665    /// ```rust
666    /// # #[cfg(feature = "std")]
667    /// # {
668    /// use deep_time::{Dt, TimeTraits};
669    ///
670    /// // ~3 days in the future relative to the system clock
671    /// let future = 3.days().from_now();
672    /// assert!(future > Dt::now());
673    /// # }
674    /// ```
675    ///
676    /// ## See also
677    ///
678    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
679    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
680    /// - [`Dt::now`](../struct.Dt.html#method.now)
681    #[cfg(any(
682        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
683        all(target_arch = "wasm32", feature = "js"),
684    ))]
685    #[inline]
686    pub fn from_now(self) -> Dt {
687        Dt::now().add(self)
688    }
689}