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_F, SEC_PER_DAY_I64, SEC_PER_WEEK, Scale, TAI_SEC_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 [`Dt::ZERO`](#associatedconstant.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_SEC_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 [`Dt::ZERO`](#associatedconstant.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 = Self::new(
36        -3_155_716_800_000_000_000_000_000_000i128,
37        Scale::TAI,
38        Scale::TAI,
39    );
40
41    /// TT/TCG/TCB/TDB epoch.
42    ///
43    /// - 1977-01-01 00:00:00 TAI.
44    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
45    /// - -725_803_200_000_000_000_000_000_000 attoseconds
46    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
47    pub const TAI_1977_EPOCH: Self = Self::new(
48        -725_803_200_000_000_000_000_000_000i128,
49        Scale::TAI,
50        Scale::TAI,
51    );
52
53    /// Chandra X-ray Center (CXC) Time epoch.
54    ///
55    /// - 1998-01-01 00:00:00 TT.
56    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
57    /// - -63_115_232_184_000_000_000_000_000_000 attoseconds
58    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
59    pub const CXC_EPOCH: Self = Self::new(
60        -63_115_232_184_000_000_000_000_000i128,
61        Scale::TAI,
62        Scale::TT,
63    );
64
65    /// GPS/Galileo Experiment (GALEX) Time epoch.
66    ///
67    /// - 1980-01-06 00:00:00 UTC.
68    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
69    /// - -630_763_181_000_000_000_000_000_000 attoseconds
70    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
71    pub const GPS_EPOCH: Self = Self::new(
72        -630_763_181_000_000_000_000_000_000i128,
73        Scale::TAI,
74        Scale::GPS,
75    );
76
77    /// Galileo System Time (GST) epoch.
78    ///
79    /// - 1999-08-22 00:00:00 GST.
80    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
81    /// - -11_447_981_000_000_000_000_000_000 attoseconds
82    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
83    pub const GALILEO_EPOCH: Self = Self::new(
84        -11_447_981_000_000_000_000_000_000i128,
85        Scale::TAI,
86        Scale::GST,
87    );
88
89    /// BeiDou Time (BDT) epoch.
90    ///
91    /// - 2006-01-01 00:00:00 UTC.
92    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
93    /// - 189_345_633_000_000_000_000_000_000 attoseconds
94    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
95    pub const BDT_EPOCH: Self = Self::new(
96        189_345_633_000_000_000_000_000_000i128,
97        Scale::TAI,
98        Scale::BDT,
99    );
100
101    /// CCSDS epoch (used in CCSDS time codes such as CUC).
102    ///
103    /// - 1958-01-01 00:00:00 TAI.
104    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
105    /// - -1_325_419_200_000_000_000_000_000_000 attoseconds
106    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
107    pub const CCSDS_EPOCH: Self = Self::new(
108        -1_325_419_200_000_000_000_000_000_000i128,
109        Scale::TAI,
110        Scale::TAI,
111    );
112
113    /// JD epoch (JD 0.0).
114    ///
115    /// - -4713-11-24 12:00:00
116    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
117    /// - -211_813_488_000_000_000_000_000_000 attoseconds
118    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
119    pub const JD_EPOCH: Self = Self::new(
120        -211_813_488_000_000_000_000_000_000_000i128,
121        Scale::TAI,
122        Scale::TAI,
123    );
124
125    /// MJD epoch (MJD 0.0)
126    ///
127    /// - 1858-11-17 00:00:00
128    /// - Stored here on the **TAI** timescale as an offset from [`Dt::ZERO`](#associatedconstant.ZERO).
129    /// - -4_453_444_800_000_000_000_000_000_000 attoseconds
130    /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
131    pub const MJD_EPOCH: Self = Self::new(
132        -4_453_444_800_000_000_000_000_000_000i128,
133        Scale::TAI,
134        Scale::TAI,
135    );
136
137    /// Maximum representable duration.
138    pub const MAX: Self = Self::new(i128::MAX, Scale::TAI, Scale::TAI);
139
140    /// Minimum (most negative) representable duration.
141    pub const MIN: Self = Self::new(i128::MIN, Scale::TAI, Scale::TAI);
142
143    /// 19 seconds.
144    pub const SEC_19: Self = Self::new(19i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
145
146    /// 33 seconds.
147    pub const SEC_33: Self = Self::new(33i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
148
149    /// 37 seconds.
150    pub const SEC_37: Self = Self::new(37i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
151
152    /// One days worth of attoseconds.
153    pub const ONE_DAY: Self = Self::new(
154        (SEC_PER_DAY_I64 as i128) * ATTOS_PER_SEC_I128,
155        Scale::TAI,
156        Scale::TAI,
157    );
158
159    /// Creates a new [`Dt`] from a total number of attoseconds since the librarys
160    /// epoch **2000-01-01 12:00:00 TAI**.
161    ///
162    /// Does **not** perform any time scale conversions.
163    ///
164    /// ## Examples
165    ///
166    /// ```rust
167    /// use deep_time::{Dt, Scale};
168    ///
169    /// // current scale TAI, target scale UTC
170    /// let a = Dt::new(0, Scale::TAI, Scale::UTC);
171    ///
172    /// // equivalent to direct construction
173    /// let b = Dt { attos: 0, scale: Scale::TAI, target: Scale::UTC };
174    ///
175    /// assert_eq!(a, b);
176    /// ```
177    ///
178    /// ## See also
179    ///
180    /// - [`dt!`](../macro.dt.html)
181    /// - [`ns!`](../macro.ns.html)
182    #[inline(always)]
183    pub const fn new(attos: i128, scale: Scale, target: Scale) -> Dt {
184        Dt {
185            attos,
186            scale,
187            target,
188        }
189    }
190
191    /// Low level constructor from total attoseconds since a given epoch.
192    ///
193    /// Simply adds the total attoseconds to the epoch. Does not perform
194    /// any time scale conversions.
195    ///
196    /// The returned [`Dt`] copies the epoch's `scale` and `target` fields.
197    ///
198    /// ## Examples
199    ///
200    /// ```rust
201    /// use deep_time::{Dt, Scale};
202    ///
203    /// // A leap second from the middle of the table (36 leap seconds accumulated)
204    /// let original = Dt::from_ymd(2015, 6, 30, Scale::UTC, 23, 59, 60, 123_456_789_000_000_000);
205    ///
206    /// // Round-trip through canonical attoseconds
207    /// let canon = original.to_diff_raw(Dt::UNIX_EPOCH).to_attos();
208    /// let roundtrip1 = Dt::from_diff_raw(canon, Dt::UNIX_EPOCH);
209    ///
210    /// assert_eq!(original, roundtrip1, "Canonical round-trip failed");
211    /// ```
212    ///
213    /// ## See also
214    ///
215    /// - [`Dt::to_diff_raw`](../struct.Dt.html#method.to_diff_raw)
216    /// - [`Dt::to_diff_raw_f`](../struct.Dt.html#method.to_diff_raw_f)
217    #[inline(always)]
218    pub const fn from_diff_raw(attos: i128, epoch: Dt) -> Dt {
219        epoch.add(Dt::new(attos, epoch.scale, epoch.target))
220    }
221
222    /// Builds a [`Dt`] holding the given whole seconds and sub-second remainder.
223    ///
224    /// The remainder is in **attoseconds**, not seconds. Pairs with
225    /// [`Dt::to_sec64`](#method.to_sec64) + [`Dt::to_sec_frac`](#method.to_sec_frac).
226    ///
227    /// Does **not** perform any time scale conversions.
228    ///
229    /// ## Parameters
230    ///
231    /// - `sec` — whole seconds (truncating / signed-remainder split).
232    /// - `attos` — fractional part of that split, in attoseconds.
233    ///   Prefer helpers such as [`Dt::ms_to_attos`](#method.ms_to_attos) /
234    ///   [`Dt::ns_to_attos`](#method.ns_to_attos) or the [`ms!`](../macro.ms.html) /
235    ///   [`ns!`](../macro.ns.html) macros instead of hand-counting zeros:
236    ///   - `1.3` s → `sec = 1`, `attos = Dt::ms_to_attos(300)`
237    ///   - `-1.3` s → `sec = -1`, `attos = Dt::ms_to_attos(-300)`
238    ///   - `-0.5` s → `sec = 0`, `attos = Dt::ms_to_attos(-500)`
239    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
240    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
241    ///
242    /// ## Examples
243    ///
244    /// ```rust
245    /// use deep_time::{Dt, Scale};
246    /// use deep_time::macros::{dt, ms};
247    ///
248    /// // 1.3 s — convert 300 ms of remainder to attoseconds
249    /// let a = Dt::from_sec_and_frac(1, ms!(300), Scale::TAI, Scale::TAI);
250    /// assert_eq!(a, dt!(1_300_000_000_000_000_000));
251    ///
252    /// // -1.3 s (signed remainder)
253    /// assert_eq!(
254    ///     Dt::from_sec_and_frac(-1, Dt::ms_to_attos(-300), Scale::TAI, Scale::TAI),
255    ///     dt!(-1_300_000_000_000_000_000),
256    /// );
257    ///
258    /// // -0.5 s
259    /// assert_eq!(
260    ///     Dt::from_sec_and_frac(0, Dt::ms_to_attos(-500), Scale::TAI, Scale::TAI),
261    ///     dt!(-500_000_000_000_000_000),
262    /// );
263    /// ```
264    #[inline(always)]
265    pub const fn from_sec_and_frac(sec: i128, attos: i128, on: Scale, target: Scale) -> Dt {
266        Dt::new(Self::sec_and_frac_to_attos(sec, attos), on, target)
267    }
268
269    /// Combines whole seconds and an attosecond remainder into total attoseconds.
270    ///
271    /// Reverse of [`to_sec_floor`](../struct.Dt.html#method.to_sec_floor) +
272    /// [`to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac). Handles the full
273    /// range of [`Dt`], including near [`i128::MIN`] where `seconds × 10¹⁸` alone
274    /// does not fit in an `i128`.
275    #[inline]
276    pub(crate) const fn sec_and_frac_to_attos(sec: i128, attos: i128) -> i128 {
277        match sec.checked_mul(ATTOS_PER_SEC_I128) {
278            Some(s) => s.saturating_add(attos),
279            None => Self::sec_and_frac_to_attos_overflow(sec, attos),
280        }
281    }
282
283    /// When `seconds × 10¹⁸` does not fit in an `i128` on its own
284    const fn sec_and_frac_to_attos_overflow(sec: i128, attos: i128) -> i128 {
285        if sec > 0 {
286            i128::MAX.saturating_add(attos)
287        } else if let Some(sec1) = sec.checked_add(1)
288            && let Some(base) = sec1.checked_mul(ATTOS_PER_SEC_I128)
289        {
290            // near the lowest Dt, (sec+1)×10¹⁸ plus a reduced remainder still
291            // fits and gives the correct total
292            base.saturating_add(attos.saturating_sub(ATTOS_PER_SEC_I128))
293        } else {
294            i128::MIN.saturating_add(attos)
295        }
296    }
297
298    /// Builds a [`Dt`] holding the given whole seconds.
299    ///
300    /// Does **not** perform any time scale conversions. The `sec` count is stored
301    /// as-is (converted only from seconds to attoseconds); its meaning depends on
302    /// how you use the value afterward (for example as a library-epoch offset, a
303    /// Unix offset passed to [`Dt::from_unix`](#method.from_unix), a duration, etc.).
304    ///
305    /// ## Parameters
306    ///
307    /// - `sec` — whole seconds count to store.
308    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
309    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
310    #[inline(always)]
311    pub const fn from_sec(sec: i128, on: Scale, target: Scale) -> Dt {
312        Dt::new(sec.saturating_mul(ATTOS_PER_SEC_I128), on, target)
313    }
314
315    /// Builds a [`Dt`] holding the given whole milliseconds and sub-millisecond remainder.
316    ///
317    /// The remainder is in **attoseconds**, not milliseconds. Pairs with
318    /// [`to_ms`](../struct.Dt.html#method.to_ms).
319    ///
320    /// Does **not** perform any time scale conversions.
321    ///
322    /// ## Parameters
323    ///
324    /// - `ms` — whole milliseconds (truncating / signed-remainder split).
325    /// - `frac_attos` — fractional part of that split, in attoseconds.
326    ///   Use a smaller-unit converter rather than counting zeros by hand:
327    ///   - `1.3` ms → `ms = 1`, `frac_attos = Dt::us_to_attos(300)` (0.3 ms = 300 µs)
328    ///   - `-1.3` ms → `ms = -1`, `frac_attos = Dt::us_to_attos(-300)`
329    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
330    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
331    ///
332    /// ## Examples
333    ///
334    /// ```rust
335    /// use deep_time::{Dt, Scale};
336    /// use deep_time::macros::us;
337    ///
338    /// // 1.3 ms
339    /// let a = Dt::from_ms(1, us!(300), Scale::TAI, Scale::TAI);
340    /// assert_eq!(a.to_attos(), 1_300_000_000_000_000);
341    ///
342    /// // -1.3 ms
343    /// let neg = Dt::from_ms(-1, us!(-300), Scale::TAI, Scale::TAI);
344    /// assert_eq!(neg.to_attos(), -1_300_000_000_000_000);
345    ///
346    /// // or as floored -1.3 ms
347    /// let neg = Dt::from_ms(-2, us!(700), Scale::TAI, Scale::TAI);
348    /// assert_eq!(neg.to_attos(), -1_300_000_000_000_000);
349    /// ```
350    ///
351    /// ## See also
352    ///
353    /// - [`from_ms!`](../macros/macro.from_ms.html)
354    /// - [`us!`](../macros/macro.us.html)
355    #[inline(always)]
356    pub const fn from_ms(ms: i128, attos: i128, on: Scale, target: Scale) -> Dt {
357        let attos = Dt::unit_to_total_attos(ms, attos, ATTOS_PER_MS_I128);
358        Dt::new(attos, on, target)
359    }
360
361    /// Builds a [`Dt`] holding the given whole microseconds and sub-microsecond remainder.
362    ///
363    /// The remainder is in **attoseconds**, not microseconds. Pairs with
364    /// [`to_us`](../struct.Dt.html#method.to_us).
365    ///
366    /// Does **not** perform any time scale conversions.
367    ///
368    /// ## Parameters
369    ///
370    /// - `us` — whole microseconds (truncating / signed-remainder split).
371    /// - `frac_attos` — fractional part of that split, in attoseconds.
372    ///   Use a smaller-unit converter rather than counting zeros by hand:
373    ///   - `1.3` µs → `us = 1`, `frac_attos = Dt::ns_to_attos(300)` (0.3 µs = 300 ns)
374    ///   - `-1.3` µs → `us = -1`, `frac_attos = Dt::ns_to_attos(-300)`
375    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
376    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
377    ///
378    /// ## Examples
379    ///
380    /// ```rust
381    /// use deep_time::{Dt, Scale};
382    /// use deep_time::macros::ns;
383    ///
384    /// // 1.3 µs
385    /// let a = Dt::from_us(1, ns!(300), Scale::TAI, Scale::TAI);
386    /// assert_eq!(a.to_attos(), 1_300_000_000_000);
387    ///
388    /// // -1.3 µs
389    /// let neg = Dt::from_us(-1, ns!(-300), Scale::TAI, Scale::TAI);
390    /// assert_eq!(neg.to_attos(), -1_300_000_000_000);
391    ///
392    /// // or as floored -1.3 µs
393    /// let neg = Dt::from_us(-2, ns!(700), Scale::TAI, Scale::TAI);
394    /// assert_eq!(neg.to_attos(), -1_300_000_000_000);
395    /// ```
396    ///
397    /// ## See also
398    ///
399    /// - [`from_us!`](../macros/macro.from_us.html)
400    /// - [`ns!`](../macro.ns.html)
401    #[inline(always)]
402    pub const fn from_us(us: i128, attos: i128, on: Scale, target: Scale) -> Dt {
403        let attos = Dt::unit_to_total_attos(us, attos, ATTOS_PER_US_I128);
404        Dt::new(attos, on, target)
405    }
406
407    /// Builds a [`Dt`] holding the given whole nanoseconds and sub-nanosecond remainder.
408    ///
409    /// The remainder is in **attoseconds**, not nanoseconds. Pairs with
410    /// [`to_ns`](../struct.Dt.html#method.to_ns).
411    ///
412    /// Does **not** perform any time scale conversions.
413    ///
414    /// ## Parameters
415    ///
416    /// - `ns` — whole nanoseconds (truncating / signed-remainder split).
417    /// - `frac_attos` — fractional part of that split, in attoseconds.
418    ///   Use a smaller-unit converter rather than counting zeros by hand:
419    ///   - `1.3` ns → `ns = 1`, `frac_attos = Dt::ps_to_attos(300)` (0.3 ns = 300 ps)
420    ///   - `-1.3` ns → `ns = -1`, `frac_attos = Dt::ps_to_attos(-300)`
421    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
422    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
423    ///
424    /// ## Examples
425    ///
426    /// ```rust
427    /// use deep_time::{Dt, Scale};
428    /// use deep_time::macros::ps;
429    ///
430    /// // 1.3 ns → whole nanoseconds + 300 ps remainder
431    /// let a = Dt::from_ns(1, ps!(300), Scale::TAI, Scale::TAI);
432    /// assert_eq!(a.to_attos(), 1_300_000_000);
433    ///
434    /// // -1.3 ns
435    /// let neg = Dt::from_ns(-1, Dt::ps_to_attos(-300), Scale::TAI, Scale::TAI);
436    /// assert_eq!(neg.to_attos(), -1_300_000_000);
437    /// ```
438    #[inline(always)]
439    pub const fn from_ns(ns: i128, attos: i128, on: Scale, target: Scale) -> Dt {
440        let attos = Dt::unit_to_total_attos(ns, attos, ATTOS_PER_NS_I128);
441        Dt::new(attos, on, target)
442    }
443
444    /// Builds a [`Dt`] holding the given whole picoseconds and sub-picosecond remainder.
445    ///
446    /// The remainder is in **attoseconds**, not picoseconds. Pairs with
447    /// [`to_ps`](../struct.Dt.html#method.to_ps).
448    ///
449    /// Does **not** perform any time scale conversions.
450    ///
451    /// ## Parameters
452    ///
453    /// - `ps` — whole picoseconds (truncating / signed-remainder split).
454    /// - `frac_attos` — fractional part of that split, in attoseconds.
455    ///   Use a smaller-unit converter rather than counting zeros by hand:
456    ///   - `1.3` ps → `ps = 1`, `frac_attos = Dt::fs_to_attos(300)` (0.3 ps = 300 fs)
457    ///   - `-1.3` ps → `ps = -1`, `frac_attos = Dt::fs_to_attos(-300)`
458    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
459    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
460    ///
461    /// ## Examples
462    ///
463    /// ```rust
464    /// use deep_time::{Dt, Scale};
465    /// use deep_time::macros::fs;
466    ///
467    /// // 1.3 ps
468    /// let a = Dt::from_ps(1, fs!(300), Scale::TAI, Scale::TAI);
469    /// assert_eq!(a.to_attos(), 1_300_000);
470    ///
471    /// // -1.3 ps
472    /// let neg = Dt::from_ps(-1, Dt::fs_to_attos(-300), Scale::TAI, Scale::TAI);
473    /// assert_eq!(neg.to_attos(), -1_300_000);
474    /// ```
475    #[inline(always)]
476    pub const fn from_ps(ps: i128, attos: i128, on: Scale, target: Scale) -> Dt {
477        let attos = Dt::unit_to_total_attos(ps, attos, ATTOS_PER_PS_I128);
478        Dt::new(attos, on, target)
479    }
480
481    /// Builds a [`Dt`] holding the given whole femtoseconds and sub-femtosecond remainder.
482    ///
483    /// The remainder is in **attoseconds**, not femtoseconds. Pairs with
484    /// [`to_fs`](../struct.Dt.html#method.to_fs).
485    ///
486    /// Does **not** perform any time scale conversions.
487    ///
488    /// ## Parameters
489    ///
490    /// - `fs` — whole femtoseconds (truncating / signed-remainder split).
491    /// - `frac_attos` — fractional part of that split, in attoseconds.
492    ///   One femtosecond is 1000 attoseconds, so a fractional remainder is already
493    ///   a small integer: `1.3` fs → `fs = 1`, `frac_attos = 300`.
494    ///   For `-1.3` fs: `fs = -1`, `frac_attos = -300`.
495    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
496    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
497    ///
498    /// ## Examples
499    ///
500    /// ```rust
501    /// use deep_time::{Dt, Scale};
502    ///
503    /// // 1.3 fs — sub-fs remainder is already in attoseconds (×10³)
504    /// let a = Dt::from_fs(1, 300, Scale::TAI, Scale::TAI);
505    /// assert_eq!(a.to_attos(), 1_300);
506    ///
507    /// // whole fs only — still fine to use the converter for the whole part
508    /// // if you are building total attos by hand:
509    /// assert_eq!(Dt::fs_to_attos(1), 1_000);
510    ///
511    /// // -1.3 fs
512    /// let neg = Dt::from_fs(-1, -300, Scale::TAI, Scale::TAI);
513    /// assert_eq!(neg.to_attos(), -1_300);
514    /// ```
515    #[inline(always)]
516    pub const fn from_fs(fs: i128, attos: i128, on: Scale, target: Scale) -> Dt {
517        let attos = Dt::unit_to_total_attos(fs, attos, ATTOS_PER_FS_I128);
518        Dt::new(attos, on, target)
519    }
520
521    /// Builds a [`Dt`] holding the given whole minutes and sub-minute remainder.
522    ///
523    /// The remainder is in **attoseconds**, not minutes.
524    ///
525    /// Does **not** perform any time scale conversions.
526    ///
527    /// ## Parameters
528    ///
529    /// - `n` — whole minutes (truncating / signed-remainder split).
530    /// - `frac_attos` — fractional part of that split, in attoseconds.
531    ///   Use a time-unit converter rather than counting zeros by hand:
532    ///   - `1.5` min → `n = 1`, `frac_attos = Dt::sec_to_attos(30)` (0.5 min = 30 s)
533    ///   - `-1.5` min → `n = -1`, `frac_attos = Dt::sec_to_attos(-30)`
534    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
535    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
536    ///
537    /// ## Examples
538    ///
539    /// ```rust
540    /// use deep_time::{Dt, Scale};
541    /// use deep_time::macros::sec;
542    ///
543    /// // 1.5 min
544    /// let a = Dt::from_mins(1, sec!(30), Scale::TAI, Scale::TAI);
545    /// assert_eq!(a.to_sec(), 90);
546    ///
547    /// // -1.5 min
548    /// let neg = Dt::from_mins(-1, Dt::sec_to_attos(-30), Scale::TAI, Scale::TAI);
549    /// assert_eq!(neg.to_sec(), -90);
550    /// ```
551    #[inline(always)]
552    pub const fn from_mins(n: i128, attos: i128, on: Scale, target: Scale) -> Dt {
553        let attos = Dt::unit_to_total_attos(n, attos, ATTOS_PER_MIN);
554        Dt::new(attos, on, target)
555    }
556
557    /// Builds a [`Dt`] holding the given whole hours and sub-hour remainder.
558    ///
559    /// The remainder is in **attoseconds**, not hours.
560    ///
561    /// Does **not** perform any time scale conversions.
562    ///
563    /// ## Parameters
564    ///
565    /// - `n` — whole hours (truncating / signed-remainder split).
566    /// - `frac_attos` — fractional part of that split, in attoseconds.
567    ///   Use a time-unit converter rather than counting zeros by hand:
568    ///   - `1.5` h → `n = 1`, `frac_attos = Dt::mins_to_attos(30)` (0.5 h = 30 min)
569    ///   - `-1.5` h → `n = -1`, `frac_attos = Dt::mins_to_attos(-30)`
570    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
571    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
572    ///
573    /// ## Examples
574    ///
575    /// ```rust
576    /// use deep_time::{Dt, Scale};
577    /// use deep_time::macros::mins;
578    ///
579    /// // 1.5 h
580    /// let a = Dt::from_hours(1, mins!(30), Scale::TAI, Scale::TAI);
581    /// assert_eq!(a.to_sec(), 5400);
582    ///
583    /// // -1.5 h
584    /// let neg = Dt::from_hours(-1, Dt::mins_to_attos(-30), Scale::TAI, Scale::TAI);
585    /// assert_eq!(neg.to_sec(), -5400);
586    /// ```
587    #[inline(always)]
588    pub const fn from_hours(n: i128, attos: i128, on: Scale, target: Scale) -> Dt {
589        let attos = Dt::unit_to_total_attos(n, attos, ATTOS_PER_HOUR);
590        Dt::new(attos, on, target)
591    }
592
593    /// Builds a [`Dt`] holding the given whole days and sub-day remainder.
594    ///
595    /// The remainder is in **attoseconds**, not days. Uses `86400` seconds per day.
596    ///
597    /// Does **not** perform any time scale conversions.
598    ///
599    /// ## Parameters
600    ///
601    /// - `d` — whole days (truncating / signed-remainder split).
602    /// - `frac` — fractional part in attoseconds (`frac.attos` only).
603    ///   - `1.25` d → `d = 1`, `frac = dt!(Dt::hours_to_attos(6))` (0.25 d = 6 h)
604    ///   - `-1.25` d → `d = -1`, `frac = dt!(Dt::hours_to_attos(-6))`
605    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
606    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
607    ///
608    /// ## Examples
609    ///
610    /// ```rust
611    /// use deep_time::{Dt, Scale};
612    /// use deep_time::macros::{dt, hours};
613    ///
614    /// // 1.25 d
615    /// let a = Dt::from_days(1, dt!(hours!(6)), Scale::TAI, Scale::TAI);
616    /// assert_eq!(a.to_sec(), 108_000); // 1.25 * 86400
617    ///
618    /// // -1.25 d
619    /// let neg = Dt::from_days(-1, dt!(Dt::hours_to_attos(-6)), Scale::TAI, Scale::TAI);
620    /// assert_eq!(neg.to_sec(), -108_000);
621    /// ```
622    #[inline(always)]
623    pub const fn from_days(d: i128, frac: Dt, on: Scale, target: Scale) -> Dt {
624        let attos = Dt::unit_to_total_attos(d, frac.attos, ATTOS_PER_DAY);
625        Dt::new(attos, on, target)
626    }
627
628    /// Builds a [`Dt`] from a floating-point day count since the library epoch
629    /// (2000-01-01 12:00:00 TAI).
630    ///
631    /// This is the inverse of [`Dt::to_days_f`](../struct.Dt.html#method.to_days_f).
632    ///
633    /// Does **not** perform any time scale conversions.
634    ///
635    /// ## Parameters
636    ///
637    /// - `days` — day count to store (converted to attoseconds).
638    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
639    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
640    ///
641    /// ## Examples
642    ///
643    /// ```rust
644    /// use deep_time::{Dt, Scale};
645    ///
646    /// let dt = Dt::from_days_f(1.25, Scale::TAI, Scale::TAI);
647    /// assert_eq!(dt.to_days_f(), 1.25);
648    ///
649    /// let neg = Dt::from_days_f(-1.25, Scale::TAI, Scale::TAI);
650    /// assert_eq!(neg.to_days_f(), -1.25);
651    /// ```
652    ///
653    /// ## See also
654    ///
655    /// - [`Dt::from_days`](../struct.Dt.html#method.from_days)
656    /// - [`Dt::to_days_f`](../struct.Dt.html#method.to_days_f)
657    #[inline]
658    pub const fn from_days_f(days: Real, on: Scale, target: Scale) -> Dt {
659        Self::from_sec_f(days * SEC_PER_DAY_F, on, target)
660    }
661
662    /// Builds a [`Dt`] holding the given number of weeks (`604800` seconds each).
663    ///
664    /// Does **not** perform any time scale conversions.
665    ///
666    /// ## Parameters
667    ///
668    /// - `n` — whole weeks.
669    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
670    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
671    #[inline(always)]
672    pub const fn from_weeks(n: i128, on: Scale, target: Scale) -> Dt {
673        Dt::new(
674            n.saturating_mul(SEC_PER_WEEK as i128)
675                .saturating_mul(ATTOS_PER_SEC_I128),
676            on,
677            target,
678        )
679    }
680
681    /// Builds a [`Dt`] holding the given number of Julian years (`31_557_600` seconds each).
682    ///
683    /// Does **not** perform any time scale conversions.
684    ///
685    /// ## Parameters
686    ///
687    /// - `n` — whole years.
688    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
689    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
690    #[inline(always)]
691    pub const fn from_years(n: i128, on: Scale, target: Scale) -> Dt {
692        Dt::new(
693            n.saturating_mul(31_557_600)
694                .saturating_mul(ATTOS_PER_SEC_I128),
695            on,
696            target,
697        )
698    }
699
700    /// Returns an instant that is this duration **before** zero attoseconds on `scale`.
701    ///
702    /// Zero attoseconds is the library epoch **2000-01-01 12:00:00** (see
703    /// [`Dt::ZERO`](../struct.Dt.html#associatedconstant.ZERO)).
704    ///
705    /// This method does **not** read the system clock.
706    ///
707    /// For wall-clock “N units ago”, use [`Dt::ago`](../struct.Dt.html#method.ago)
708    /// (requires `std`, or WASM with `js`).
709    ///
710    /// ## Examples
711    ///
712    /// ```rust
713    /// use deep_time::{Dt, Scale, TraitsTime};
714    ///
715    /// let t = 5.sec().before_zero(Scale::TAI);
716    /// assert_eq!(t, Dt::ZERO.sub(5.sec()));
717    /// assert_eq!(t.to_sec(), -5);
718    /// ```
719    ///
720    /// ## See also
721    ///
722    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
723    #[inline(always)]
724    pub const fn before_zero(self, scale: Scale) -> Dt {
725        Dt::new(0, scale, scale).to_tai().sub(self)
726    }
727
728    /// Returns the saturating negation of this [`Dt`].
729    #[inline(always)]
730    pub const fn neg(self) -> Dt {
731        Dt::new(self.attos.saturating_neg(), self.scale, self.target)
732    }
733
734    /// Returns the saturating positive of this [`Dt`].
735    #[inline(always)]
736    pub const fn abs(self) -> Dt {
737        Dt::new(self.attos.saturating_abs(), self.scale, self.target)
738    }
739
740    /// Builds a [`Dt`] holding the given floating-point seconds count.
741    ///
742    /// Does **not** perform any time scale conversions. The `sec` value is
743    /// stored as attoseconds only; its meaning depends on how you use the
744    /// result afterward.
745    ///
746    /// ## Parameters
747    ///
748    /// - `sec` — seconds count to store (`NaN` → zero attoseconds;
749    ///   `±∞` → [`i128::MAX`] / [`i128::MIN`]).
750    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
751    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
752    ///
753    /// ## Examples
754    ///
755    /// ```rust
756    /// use deep_time::{Dt, Scale};
757    ///
758    /// let seconds = 5.5;
759    /// let duration = Dt::from_sec_f(seconds, Scale::TAI, Scale::TAI);
760    ///
761    /// assert_eq!(duration.to_sec_f(), seconds);
762    /// ```
763    pub const fn from_sec_f(sec: Real, on: Scale, target: Scale) -> Dt {
764        if sec.is_nan() {
765            return Self::new(0, on, target);
766        } else if sec.is_infinite() {
767            return if sec.is_sign_positive() {
768                Self::new(i128::MAX, on, target)
769            } else {
770                Self::new(i128::MIN, on, target)
771            };
772        }
773        Dt::new(Self::sec_f_to_attos(sec), on, target)
774    }
775
776    /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
777    ///
778    /// - Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
779    /// - Returns the rounded integer (round-to-nearest, ties away from zero).
780    pub const fn sec_f_to_attos(sec: Real) -> i128 {
781        if sec == 0.0 {
782            return 0;
783        }
784
785        let bits = sec.to_bits();
786        let is_negative = (bits >> 63) != 0;
787        let biased_exp = ((bits >> 52) & 0x7ff) as i32;
788        let mantissa = bits & 0x000f_ffff_ffff_ffff;
789
790        let (sig, exp) = if biased_exp == 0 {
791            if mantissa == 0 {
792                return 0;
793            }
794            (mantissa as u128, -1022i32 - 52)
795        } else {
796            let sig = ((1u64 << 52) | mantissa) as u128;
797            (sig, biased_exp - 1023 - 52)
798        };
799
800        const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
801        let product = sig * FIVE_POW_18;
802        let total_exp = exp + 18;
803
804        // Safe saturation / underflow guards (prevents invalid shifts >= 128)
805        if total_exp > 120 {
806            return if is_negative { i128::MIN } else { i128::MAX };
807        }
808        if total_exp < -97 {
809            return 0;
810        }
811
812        // Keep abs_total as a magnitude only (>= 0). If the value cannot fit in
813        // i128 attoseconds, return MIN or MAX here. Putting MIN into abs_total and
814        // later doing `-abs_total` for a negative input overflows.
815        let abs_total = if total_exp >= 0 {
816            let shift = total_exp as u32;
817            if product > (u128::MAX >> shift) {
818                return if is_negative { i128::MIN } else { i128::MAX };
819            }
820            let shifted = product << shift;
821            if shifted > i128::MAX as u128 {
822                return if is_negative { i128::MIN } else { i128::MAX };
823            }
824            shifted as i128
825        } else {
826            let shift = (-total_exp) as u32;
827            let int_part = (product >> shift) as i128;
828
829            // Round to nearest, half away from zero (on the absolute value)
830            let mask = (1u128 << shift) - 1;
831            let rem = product & mask;
832            if rem > (mask >> 1) {
833                int_part + 1
834            } else {
835                int_part
836            }
837        };
838
839        // abs_total ∈ [0, i128::MAX] — plain negate is safe
840        if is_negative { -abs_total } else { abs_total }
841    }
842
843    /// Returns the current **UTC** system time as TAI from 2000-01-01 12:00:00.
844    ///
845    /// This method is only available when the `std` feature is enabled and the target
846    /// is not WASM with the `js` feature.
847    #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
848    pub fn now() -> Dt {
849        use crate::macros::{from_sec, ns};
850
851        let now = std::time::SystemTime::now();
852
853        let (sec, nanos) = match now.duration_since(std::time::UNIX_EPOCH) {
854            Ok(dur) => (dur.as_secs() as i128, dur.subsec_nanos() as i128),
855            Err(e) => {
856                let dur = e.duration();
857                (-(dur.as_secs() as i128), -(dur.subsec_nanos() as i128))
858            }
859        };
860        Dt::from_diff_and_scale(
861            from_sec!(sec, ns!(nanos), on = Scale::UTC),
862            Dt::UNIX_EPOCH,
863            false,
864        )
865    }
866
867    /// Returns the current **UTC** system time as TAI from 2000-01-01 12:00:00.
868    /// (browser WASM version using JavaScript’s `Date.now()`).
869    #[cfg(all(target_arch = "wasm32", feature = "js"))]
870    pub fn now() -> Dt {
871        use crate::macros::{from_sec, ns};
872
873        let ms: f64 = js_sys::Date::now();
874        let sec = (ms / 1000.0).floor() as i128;
875        let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
876        Dt::from_diff_and_scale(
877            from_sec!(sec as i128, ns!(nanos as i128), on = Scale::UTC),
878            Dt::UNIX_EPOCH,
879            false,
880        )
881    }
882
883    /// Returns an instant that is this duration **before** the current system time.
884    ///
885    /// Subtracts `self` from [`Dt::now`](../struct.Dt.html#method.now). Available under
886    /// the same conditions as that method: the `std` feature (non-WASM-js), or WASM with
887    /// the `js` feature.
888    ///
889    /// For a `const` offset from the library epoch (no system clock), use
890    /// [`Dt::before_zero`](../struct.Dt.html#method.before_zero).
891    ///
892    /// ## Examples
893    ///
894    /// ```rust
895    /// # #[cfg(feature = "std")]
896    /// # {
897    /// use deep_time::{Dt, TraitsTime};
898    ///
899    /// // ~3 days in the past relative to the system clock
900    /// let past = 3.days().ago();
901    /// assert!(past < Dt::now());
902    /// # }
903    /// ```
904    ///
905    /// ## See also
906    ///
907    /// - [`Dt::from_now`](../struct.Dt.html#method.from_now)
908    /// - [`Dt::before_zero`](../struct.Dt.html#method.before_zero)
909    /// - [`Dt::now`](../struct.Dt.html#method.now)
910    #[cfg(any(
911        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
912        all(target_arch = "wasm32", feature = "js"),
913    ))]
914    #[inline(always)]
915    pub fn ago(self) -> Dt {
916        Dt::now().sub(self)
917    }
918
919    /// Returns an instant that is this duration **after** the current system time.
920    ///
921    /// Adds `self` to [`Dt::now`](../struct.Dt.html#method.now). Available under the same
922    /// conditions as that method: the `std` feature (non-WASM-js), or WASM with the `js`
923    /// feature.
924    ///
925    /// ## Examples
926    ///
927    /// ```rust
928    /// # #[cfg(feature = "std")]
929    /// # {
930    /// use deep_time::{Dt, TraitsTime};
931    ///
932    /// // ~3 days in the future relative to the system clock
933    /// let future = 3.days().from_now();
934    /// assert!(future > Dt::now());
935    /// # }
936    /// ```
937    ///
938    /// ## See also
939    ///
940    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
941    /// - [`Dt::now`](../struct.Dt.html#method.now)
942    #[cfg(any(
943        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
944        all(target_arch = "wasm32", feature = "js"),
945    ))]
946    #[inline(always)]
947    pub fn from_now(self) -> Dt {
948        Dt::now().add(self)
949    }
950}