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 [`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_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 [`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 =
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 [`Dt::ZERO`](#associatedconstant.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 [`Dt::ZERO`](#associatedconstant.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 [`Dt::ZERO`](#associatedconstant.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 [`Dt::ZERO`](#associatedconstant.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 [`Dt::ZERO`](#associatedconstant.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 [`Dt::ZERO`](#associatedconstant.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    /// [`Dt::to_sec64`](#method.to_sec64) + [`Dt::to_sec_frac`](#method.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    ///   Prefer helpers such as [`Dt::ms_to_attos`](#method.ms_to_attos) /
179    ///   [`Dt::ns_to_attos`](#method.ns_to_attos) (or
180    ///   [`AttosTraits`](../trait.AttosTraits.html))
181    ///   instead of hand-counting zeros:
182    ///   - `1.3` s → `sec = 1`, `attos = Dt::ms_to_attos(300)`
183    ///   - `-1.3` s → `sec = -1`, `attos = Dt::ms_to_attos(-300)`
184    ///   - `-0.5` s → `sec = 0`, `attos = Dt::ms_to_attos(-500)`
185    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
186    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
187    ///
188    /// ## Examples
189    ///
190    /// ```rust
191    /// use deep_time::{AttosTraits, Dt, Scale, dt};
192    ///
193    /// // 1.3 s — convert 300 ms of remainder to attoseconds
194    /// let a = Dt::from_sec_and_frac(1, Dt::ms_to_attos(300), Scale::TAI, Scale::TAI);
195    /// // same via AttosTraits on the integer
196    /// let b = Dt::from_sec_and_frac(1, 300_i128.ms_to_attos(), Scale::TAI, Scale::TAI);
197    /// assert_eq!(a, b);
198    /// assert_eq!(a, dt!(1_300_000_000_000_000_000));
199    ///
200    /// // -1.3 s (signed remainder)
201    /// assert_eq!(
202    ///     Dt::from_sec_and_frac(-1, Dt::ms_to_attos(-300), Scale::TAI, Scale::TAI),
203    ///     dt!(-1_300_000_000_000_000_000),
204    /// );
205    ///
206    /// // -0.5 s
207    /// assert_eq!(
208    ///     Dt::from_sec_and_frac(0, Dt::ms_to_attos(-500), Scale::TAI, Scale::TAI),
209    ///     dt!(-500_000_000_000_000_000),
210    /// );
211    /// ```
212    #[inline(always)]
213    pub const fn from_sec_and_frac(sec: i128, attos: i128, on: Scale, target: Scale) -> Dt {
214        Dt::new(
215            sec.saturating_mul(ATTOS_PER_SEC_I128).saturating_add(attos),
216            on,
217            target,
218        )
219    }
220
221    /// Builds a [`Dt`] holding the given whole seconds.
222    ///
223    /// Does **not** perform any time scale conversions. The `sec` count is stored
224    /// as-is (converted only from seconds to attoseconds); its meaning depends on
225    /// how you use the value afterward (for example as a library-epoch offset, a
226    /// Unix offset passed to [`Dt::from_unix`](#method.from_unix), a duration, etc.).
227    ///
228    /// ## Parameters
229    ///
230    /// - `sec` — whole seconds count to store.
231    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
232    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
233    #[inline(always)]
234    pub const fn from_sec(sec: i128, on: Scale, target: Scale) -> Dt {
235        Dt::new(sec.saturating_mul(ATTOS_PER_SEC_I128), on, target)
236    }
237
238    /// Builds a [`Dt`] holding the given whole milliseconds and sub-millisecond remainder.
239    ///
240    /// The remainder is in **attoseconds**, not milliseconds. Pairs with
241    /// [`to_ms`](../struct.Dt.html#method.to_ms).
242    ///
243    /// Does **not** perform any time scale conversions.
244    ///
245    /// ## Parameters
246    ///
247    /// - `ms` — whole milliseconds (truncating / signed-remainder split).
248    /// - `frac_attos` — fractional part of that split, in attoseconds.
249    ///   Use a smaller-unit converter rather than counting zeros by hand:
250    ///   - `1.3` ms → `ms = 1`, `frac_attos = Dt::us_to_attos(300)` (0.3 ms = 300 µs)
251    ///   - `-1.3` ms → `ms = -1`, `frac_attos = Dt::us_to_attos(-300)`
252    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
253    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
254    ///
255    /// ## Examples
256    ///
257    /// ```rust
258    /// use deep_time::{AttosTraits, Dt, Scale};
259    ///
260    /// // 1.3 ms
261    /// let a = Dt::from_ms(1, Dt::us_to_attos(300), Scale::TAI, Scale::TAI);
262    /// let b = Dt::from_ms(1, 300_i128.us_to_attos(), Scale::TAI, Scale::TAI);
263    /// assert_eq!(a, b);
264    /// assert_eq!(a.to_attos(), 1_300_000_000_000_000);
265    ///
266    /// // -1.3 ms
267    /// let neg = Dt::from_ms(-1, Dt::us_to_attos(-300), Scale::TAI, Scale::TAI);
268    /// assert_eq!(neg.to_attos(), -1_300_000_000_000_000);
269    /// ```
270    #[inline(always)]
271    pub const fn from_ms(ms: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
272        let attos = Dt::unit_and_signed_attos_to_attos(ms, frac_attos, ATTOS_PER_MS_I128);
273        Dt::new(attos, on, target)
274    }
275
276    /// Builds a [`Dt`] holding the given whole microseconds and sub-microsecond remainder.
277    ///
278    /// The remainder is in **attoseconds**, not microseconds. Pairs with
279    /// [`to_us`](../struct.Dt.html#method.to_us).
280    ///
281    /// Does **not** perform any time scale conversions.
282    ///
283    /// ## Parameters
284    ///
285    /// - `us` — whole microseconds (truncating / signed-remainder split).
286    /// - `frac_attos` — fractional part of that split, in attoseconds.
287    ///   Use a smaller-unit converter rather than counting zeros by hand:
288    ///   - `1.3` µs → `us = 1`, `frac_attos = Dt::ns_to_attos(300)` (0.3 µs = 300 ns)
289    ///   - `-1.3` µs → `us = -1`, `frac_attos = Dt::ns_to_attos(-300)`
290    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
291    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
292    ///
293    /// ## Examples
294    ///
295    /// ```rust
296    /// use deep_time::{AttosTraits, Dt, Scale};
297    ///
298    /// // 1.3 µs
299    /// let a = Dt::from_us(1, Dt::ns_to_attos(300), Scale::TAI, Scale::TAI);
300    /// let b = Dt::from_us(1, 300_i128.ns_to_attos(), Scale::TAI, Scale::TAI);
301    /// assert_eq!(a, b);
302    /// assert_eq!(a.to_attos(), 1_300_000_000_000);
303    ///
304    /// // -1.3 µs
305    /// let neg = Dt::from_us(-1, Dt::ns_to_attos(-300), Scale::TAI, Scale::TAI);
306    /// assert_eq!(neg.to_attos(), -1_300_000_000_000);
307    /// ```
308    #[inline(always)]
309    pub const fn from_us(us: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
310        let attos = Dt::unit_and_signed_attos_to_attos(us, frac_attos, ATTOS_PER_US_I128);
311        Dt::new(attos, on, target)
312    }
313
314    /// Builds a [`Dt`] holding the given whole nanoseconds and sub-nanosecond remainder.
315    ///
316    /// The remainder is in **attoseconds**, not nanoseconds. Pairs with
317    /// [`to_ns`](../struct.Dt.html#method.to_ns).
318    ///
319    /// Does **not** perform any time scale conversions.
320    ///
321    /// ## Parameters
322    ///
323    /// - `ns` — whole nanoseconds (truncating / signed-remainder split).
324    /// - `frac_attos` — fractional part of that split, in attoseconds.
325    ///   Use a smaller-unit converter rather than counting zeros by hand:
326    ///   - `1.3` ns → `ns = 1`, `frac_attos = Dt::ps_to_attos(300)` (0.3 ns = 300 ps)
327    ///   - `-1.3` ns → `ns = -1`, `frac_attos = Dt::ps_to_attos(-300)`
328    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
329    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
330    ///
331    /// ## Examples
332    ///
333    /// ```rust
334    /// use deep_time::{AttosTraits, Dt, Scale};
335    ///
336    /// // 1.3 ns → whole nanoseconds + 300 ps remainder
337    /// let a = Dt::from_ns(1, Dt::ps_to_attos(300), Scale::TAI, Scale::TAI);
338    /// let b = Dt::from_ns(1, 300_i128.ps_to_attos(), Scale::TAI, Scale::TAI);
339    /// assert_eq!(a, b);
340    /// assert_eq!(a.to_attos(), 1_300_000_000);
341    ///
342    /// // -1.3 ns
343    /// let neg = Dt::from_ns(-1, Dt::ps_to_attos(-300), Scale::TAI, Scale::TAI);
344    /// assert_eq!(neg.to_attos(), -1_300_000_000);
345    /// ```
346    #[inline(always)]
347    pub const fn from_ns(ns: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
348        let attos = Dt::unit_and_signed_attos_to_attos(ns, frac_attos, ATTOS_PER_NS_I128);
349        Dt::new(attos, on, target)
350    }
351
352    /// Builds a [`Dt`] holding the given whole picoseconds and sub-picosecond remainder.
353    ///
354    /// The remainder is in **attoseconds**, not picoseconds. Pairs with
355    /// [`to_ps`](../struct.Dt.html#method.to_ps).
356    ///
357    /// Does **not** perform any time scale conversions.
358    ///
359    /// ## Parameters
360    ///
361    /// - `ps` — whole picoseconds (truncating / signed-remainder split).
362    /// - `frac_attos` — fractional part of that split, in attoseconds.
363    ///   Use a smaller-unit converter rather than counting zeros by hand:
364    ///   - `1.3` ps → `ps = 1`, `frac_attos = Dt::fs_to_attos(300)` (0.3 ps = 300 fs)
365    ///   - `-1.3` ps → `ps = -1`, `frac_attos = Dt::fs_to_attos(-300)`
366    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
367    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
368    ///
369    /// ## Examples
370    ///
371    /// ```rust
372    /// use deep_time::{AttosTraits, Dt, Scale};
373    ///
374    /// // 1.3 ps
375    /// let a = Dt::from_ps(1, Dt::fs_to_attos(300), Scale::TAI, Scale::TAI);
376    /// let b = Dt::from_ps(1, 300_i128.fs_to_attos(), Scale::TAI, Scale::TAI);
377    /// assert_eq!(a, b);
378    /// assert_eq!(a.to_attos(), 1_300_000);
379    ///
380    /// // -1.3 ps
381    /// let neg = Dt::from_ps(-1, Dt::fs_to_attos(-300), Scale::TAI, Scale::TAI);
382    /// assert_eq!(neg.to_attos(), -1_300_000);
383    /// ```
384    #[inline(always)]
385    pub const fn from_ps(ps: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
386        let attos = Dt::unit_and_signed_attos_to_attos(ps, frac_attos, ATTOS_PER_PS_I128);
387        Dt::new(attos, on, target)
388    }
389
390    /// Builds a [`Dt`] holding the given whole femtoseconds and sub-femtosecond remainder.
391    ///
392    /// The remainder is in **attoseconds**, not femtoseconds. Pairs with
393    /// [`to_fs`](../struct.Dt.html#method.to_fs).
394    ///
395    /// Does **not** perform any time scale conversions.
396    ///
397    /// ## Parameters
398    ///
399    /// - `fs` — whole femtoseconds (truncating / signed-remainder split).
400    /// - `frac_attos` — fractional part of that split, in attoseconds.
401    ///   One femtosecond is 1000 attoseconds, so a fractional remainder is already
402    ///   a small integer: `1.3` fs → `fs = 1`, `frac_attos = 300`.
403    ///   For `-1.3` fs: `fs = -1`, `frac_attos = -300`.
404    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
405    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
406    ///
407    /// ## Examples
408    ///
409    /// ```rust
410    /// use deep_time::{Dt, Scale};
411    ///
412    /// // 1.3 fs — sub-fs remainder is already in attoseconds (×10³)
413    /// let a = Dt::from_fs(1, 300, Scale::TAI, Scale::TAI);
414    /// assert_eq!(a.to_attos(), 1_300);
415    ///
416    /// // whole fs only — still fine to use the converter for the whole part
417    /// // if you are building total attos by hand:
418    /// assert_eq!(Dt::fs_to_attos(1), 1_000);
419    ///
420    /// // -1.3 fs
421    /// let neg = Dt::from_fs(-1, -300, Scale::TAI, Scale::TAI);
422    /// assert_eq!(neg.to_attos(), -1_300);
423    /// ```
424    #[inline(always)]
425    pub const fn from_fs(fs: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
426        let attos = Dt::unit_and_signed_attos_to_attos(fs, frac_attos, ATTOS_PER_FS_I128);
427        Dt::new(attos, on, target)
428    }
429
430    /// Builds a [`Dt`] holding the given whole minutes and sub-minute remainder.
431    ///
432    /// The remainder is in **attoseconds**, not minutes.
433    ///
434    /// Does **not** perform any time scale conversions.
435    ///
436    /// ## Parameters
437    ///
438    /// - `n` — whole minutes (truncating / signed-remainder split).
439    /// - `frac_attos` — fractional part of that split, in attoseconds.
440    ///   Use a time-unit converter rather than counting zeros by hand:
441    ///   - `1.5` min → `n = 1`, `frac_attos = Dt::sec_to_attos(30)` (0.5 min = 30 s)
442    ///   - `-1.5` min → `n = -1`, `frac_attos = Dt::sec_to_attos(-30)`
443    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
444    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
445    ///
446    /// ## Examples
447    ///
448    /// ```rust
449    /// use deep_time::{AttosTraits, Dt, Scale};
450    ///
451    /// // 1.5 min
452    /// let a = Dt::from_mins(1, Dt::sec_to_attos(30), Scale::TAI, Scale::TAI);
453    /// let b = Dt::from_mins(1, 30_i128.sec_to_attos(), Scale::TAI, Scale::TAI);
454    /// assert_eq!(a, b);
455    /// assert_eq!(a.to_sec(), 90);
456    ///
457    /// // -1.5 min
458    /// let neg = Dt::from_mins(-1, Dt::sec_to_attos(-30), Scale::TAI, Scale::TAI);
459    /// assert_eq!(neg.to_sec(), -90);
460    /// ```
461    #[inline(always)]
462    pub const fn from_mins(n: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
463        let attos = Dt::unit_and_signed_attos_to_attos(n, frac_attos, ATTOS_PER_MIN);
464        Dt::new(attos, on, target)
465    }
466
467    /// Builds a [`Dt`] holding the given whole hours and sub-hour remainder.
468    ///
469    /// The remainder is in **attoseconds**, not hours.
470    ///
471    /// Does **not** perform any time scale conversions.
472    ///
473    /// ## Parameters
474    ///
475    /// - `n` — whole hours (truncating / signed-remainder split).
476    /// - `frac_attos` — fractional part of that split, in attoseconds.
477    ///   Use a time-unit converter rather than counting zeros by hand:
478    ///   - `1.5` h → `n = 1`, `frac_attos = Dt::mins_to_attos(30)` (0.5 h = 30 min)
479    ///   - `-1.5` h → `n = -1`, `frac_attos = Dt::mins_to_attos(-30)`
480    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
481    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
482    ///
483    /// ## Examples
484    ///
485    /// ```rust
486    /// use deep_time::{AttosTraits, Dt, Scale};
487    ///
488    /// // 1.5 h
489    /// let a = Dt::from_hours(1, Dt::mins_to_attos(30), Scale::TAI, Scale::TAI);
490    /// let b = Dt::from_hours(1, 30_i128.mins_to_attos(), Scale::TAI, Scale::TAI);
491    /// assert_eq!(a, b);
492    /// assert_eq!(a.to_sec(), 5400);
493    ///
494    /// // -1.5 h
495    /// let neg = Dt::from_hours(-1, Dt::mins_to_attos(-30), Scale::TAI, Scale::TAI);
496    /// assert_eq!(neg.to_sec(), -5400);
497    /// ```
498    #[inline(always)]
499    pub const fn from_hours(n: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
500        let attos = Dt::unit_and_signed_attos_to_attos(n, frac_attos, ATTOS_PER_HOUR);
501        Dt::new(attos, on, target)
502    }
503
504    /// Builds a [`Dt`] holding the given whole days and sub-day remainder.
505    ///
506    /// The remainder is in **attoseconds**, not days. Uses `86400` seconds per day.
507    ///
508    /// Does **not** perform any time scale conversions.
509    ///
510    /// ## Parameters
511    ///
512    /// - `d` — whole days (truncating / signed-remainder split).
513    /// - `frac_attos` — fractional part of that split, in attoseconds.
514    ///   Use a time-unit converter rather than counting zeros by hand:
515    ///   - `1.25` d → `d = 1`, `frac_attos = Dt::hours_to_attos(6)` (0.25 d = 6 h)
516    ///   - `-1.25` d → `d = -1`, `frac_attos = Dt::hours_to_attos(-6)`
517    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
518    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
519    ///
520    /// ## Examples
521    ///
522    /// ```rust
523    /// use deep_time::{AttosTraits, Dt, Scale};
524    ///
525    /// // 1.25 d
526    /// let a = Dt::from_days(1, Dt::hours_to_attos(6), Scale::TAI, Scale::TAI);
527    /// let b = Dt::from_days(1, 6_i128.hours_to_attos(), Scale::TAI, Scale::TAI);
528    /// assert_eq!(a, b);
529    /// assert_eq!(a.to_sec(), 108_000); // 1.25 * 86400
530    ///
531    /// // -1.25 d
532    /// let neg = Dt::from_days(-1, Dt::hours_to_attos(-6), Scale::TAI, Scale::TAI);
533    /// assert_eq!(neg.to_sec(), -108_000);
534    /// ```
535    #[inline(always)]
536    pub const fn from_days(d: i128, frac_attos: i128, on: Scale, target: Scale) -> Dt {
537        let attos = Dt::unit_and_signed_attos_to_attos(d, frac_attos, ATTOS_PER_DAY);
538        Dt::new(attos, on, target)
539    }
540
541    /// Builds a [`Dt`] holding the given number of weeks (`604800` seconds each).
542    ///
543    /// Does **not** perform any time scale conversions.
544    ///
545    /// ## Parameters
546    ///
547    /// - `n` — whole weeks.
548    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
549    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
550    #[inline(always)]
551    pub const fn from_weeks(n: i128, on: Scale, target: Scale) -> Dt {
552        Dt::new(
553            n.saturating_mul(SEC_PER_WEEK as i128)
554                .saturating_mul(ATTOS_PER_SEC_I128),
555            on,
556            target,
557        )
558    }
559
560    /// Builds a [`Dt`] holding the given number of Julian years (`31_557_600` seconds each).
561    ///
562    /// Does **not** perform any time scale conversions.
563    ///
564    /// ## Parameters
565    ///
566    /// - `n` — whole years.
567    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
568    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
569    #[inline(always)]
570    pub const fn from_years(n: i128, on: Scale, target: Scale) -> Dt {
571        Dt::new(
572            n.saturating_mul(31_557_600)
573                .saturating_mul(ATTOS_PER_SEC_I128),
574            on,
575            target,
576        )
577    }
578
579    /// Returns an instant that is this duration **before** zero attoseconds on `scale`.
580    ///
581    /// Zero attoseconds is the library epoch **2000-01-01 12:00:00** (see
582    /// [`Dt::ZERO`](../struct.Dt.html#associatedconstant.ZERO)).
583    ///
584    /// This method does **not** read the system clock.
585    ///
586    /// For wall-clock “N units ago”, use [`Dt::ago`](../struct.Dt.html#method.ago)
587    /// (requires `std`, or WASM with `js`).
588    ///
589    /// ## Examples
590    ///
591    /// ```rust
592    /// use deep_time::{Dt, Scale, TimeTraits};
593    ///
594    /// let t = 5.sec().before_zero(Scale::TAI);
595    /// assert_eq!(t, Dt::ZERO.sub(5.sec()));
596    /// assert_eq!(t.to_sec(), -5);
597    /// ```
598    ///
599    /// ## See also
600    ///
601    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
602    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
603    #[inline(always)]
604    pub const fn before_zero(self, scale: Scale) -> Dt {
605        Dt::new(0, scale, scale).to_tai().sub(self)
606    }
607
608    /// Returns the negation of this [`Dt`].
609    #[inline(always)]
610    pub const fn neg(self) -> Dt {
611        Dt::new(-self.attos, self.scale, self.target)
612    }
613
614    /// Returns the positive of this [`Dt`].
615    #[inline(always)]
616    pub const fn abs(self) -> Dt {
617        Dt::new(self.attos.saturating_abs(), self.scale, self.target)
618    }
619
620    /// Builds a [`Dt`] holding the given floating-point seconds count.
621    ///
622    /// Does **not** perform any time scale conversions. The `sec` value is
623    /// stored as attoseconds only; its meaning depends on how you use the
624    /// result afterward.
625    ///
626    /// ## Parameters
627    ///
628    /// - `sec` — seconds count to store (`NaN` → zero attoseconds;
629    ///   `±∞` → [`i128::MAX`] / [`i128::MIN`]).
630    /// - `on` — value stored in the returned [`Dt`]'s `scale` field.
631    /// - `target` — value stored in the returned [`Dt`]'s `target` field.
632    ///
633    /// ## Examples
634    ///
635    /// ```rust
636    /// use deep_time::{Dt, Scale};
637    ///
638    /// let seconds = 5.5;
639    /// let duration = Dt::from_sec_f(seconds, Scale::TAI, Scale::TAI);
640    ///
641    /// assert_eq!(duration.to_sec_f(), seconds);
642    /// ```
643    #[inline]
644    pub const fn from_sec_f(sec: Real, on: Scale, target: Scale) -> Dt {
645        if sec.is_nan() {
646            return Self::new(0, on, target);
647        } else if sec.is_infinite() {
648            return if sec.is_sign_positive() {
649                Self::new(i128::MAX, on, target)
650            } else {
651                Self::new(i128::MIN, on, target)
652            };
653        }
654        Dt::new(Self::sec_f_to_attos(sec), on, target)
655    }
656
657    /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
658    ///
659    /// - Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
660    /// - Returns the rounded integer (round-to-nearest, ties away from zero).
661    pub const fn sec_f_to_attos(sec: Real) -> i128 {
662        if sec == 0.0 {
663            return 0;
664        }
665
666        let bits = sec.to_bits();
667        let is_negative = (bits >> 63) != 0;
668        let biased_exp = ((bits >> 52) & 0x7ff) as i32;
669        let mantissa = bits & 0x000f_ffff_ffff_ffff;
670
671        let (sig, exp) = if biased_exp == 0 {
672            if mantissa == 0 {
673                return 0;
674            }
675            (mantissa as u128, -1022i32 - 52)
676        } else {
677            let sig = ((1u64 << 52) | mantissa) as u128;
678            (sig, biased_exp - 1023 - 52)
679        };
680
681        const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
682        let product = sig * FIVE_POW_18;
683        let total_exp = exp + 18;
684
685        // Safe saturation / underflow guards (prevents invalid shifts >= 128)
686        if total_exp > 120 {
687            return if is_negative { i128::MIN } else { i128::MAX };
688        }
689        if total_exp < -97 {
690            return 0;
691        }
692
693        let abs_total = if total_exp >= 0 {
694            let shift = total_exp as u32;
695            if product > (u128::MAX >> shift) {
696                if is_negative { i128::MIN } else { i128::MAX }
697            } else {
698                let shifted = product << shift;
699                if shifted > i128::MAX as u128 {
700                    if is_negative { i128::MIN } else { i128::MAX }
701                } else {
702                    shifted as i128
703                }
704            }
705        } else {
706            let shift = (-total_exp) as u32;
707            let int_part = (product >> shift) as i128;
708
709            // Round to nearest, half away from zero (on the absolute value)
710            let mask = (1u128 << shift) - 1;
711            let rem = product & mask;
712            if rem > (mask >> 1) {
713                int_part + 1
714            } else {
715                int_part
716            }
717        };
718
719        if is_negative { -abs_total } else { abs_total }
720    }
721
722    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
723    ///
724    /// This method is only available when the `std` feature is enabled and the target
725    /// is not WASM with the `js` feature.
726    #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
727    pub fn now() -> Dt {
728        let now = std::time::SystemTime::now();
729
730        let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
731            Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
732            Err(e) => {
733                let dur = e.duration();
734                (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
735            }
736        };
737
738        Dt::from_diff_and_scale(
739            Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
740            Dt::UNIX_EPOCH,
741            false,
742        )
743        .add(Dt::from_ns(nanos as i128, 0, Scale::TAI, Scale::TAI))
744    }
745
746    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
747    /// (browser WASM version using JavaScript’s `Date.now()`).
748    #[cfg(all(target_arch = "wasm32", feature = "js"))]
749    pub fn now() -> Dt {
750        let ms: f64 = js_sys::Date::now();
751        let secs = (ms / 1000.0).floor() as i128;
752        let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
753        Dt::from_diff_and_scale(
754            Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
755            Dt::UNIX_EPOCH,
756            false,
757        )
758        .add(Dt::from_ns(nanos as i128, 0, Scale::TAI, Scale::TAI))
759    }
760
761    /// Returns an instant that is this duration **before** the current system time.
762    ///
763    /// Subtracts `self` from [`Dt::now`](../struct.Dt.html#method.now). Available under
764    /// the same conditions as that method: the `std` feature (non-WASM-js), or WASM with
765    /// the `js` feature.
766    ///
767    /// For a `const` offset from the library epoch (no system clock), use
768    /// [`Dt::before_zero`](../struct.Dt.html#method.before_zero).
769    ///
770    /// ## Examples
771    ///
772    /// ```rust
773    /// # #[cfg(feature = "std")]
774    /// # {
775    /// use deep_time::{Dt, TimeTraits};
776    ///
777    /// // ~3 days in the past relative to the system clock
778    /// let past = 3.days().ago();
779    /// assert!(past < Dt::now());
780    /// # }
781    /// ```
782    ///
783    /// ## See also
784    ///
785    /// - [`Dt::from_now`](../struct.Dt.html#method.from_now)
786    /// - [`Dt::before_zero`](../struct.Dt.html#method.before_zero)
787    /// - [`Dt::now`](../struct.Dt.html#method.now)
788    #[cfg(any(
789        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
790        all(target_arch = "wasm32", feature = "js"),
791    ))]
792    #[inline]
793    pub fn ago(self) -> Dt {
794        Dt::now().sub(self)
795    }
796
797    /// Returns an instant that is this duration **after** the current system time.
798    ///
799    /// Adds `self` to [`Dt::now`](../struct.Dt.html#method.now). Available under the same
800    /// conditions as that method: the `std` feature (non-WASM-js), or WASM with the `js`
801    /// feature.
802    ///
803    /// For a `const` offset from the library epoch (no system clock), use
804    /// [`Dt::after_zero`](../struct.Dt.html#method.after_zero).
805    ///
806    /// ## Examples
807    ///
808    /// ```rust
809    /// # #[cfg(feature = "std")]
810    /// # {
811    /// use deep_time::{Dt, TimeTraits};
812    ///
813    /// // ~3 days in the future relative to the system clock
814    /// let future = 3.days().from_now();
815    /// assert!(future > Dt::now());
816    /// # }
817    /// ```
818    ///
819    /// ## See also
820    ///
821    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
822    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
823    /// - [`Dt::now`](../struct.Dt.html#method.now)
824    #[cfg(any(
825        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
826        all(target_arch = "wasm32", feature = "js"),
827    ))]
828    #[inline]
829    pub fn from_now(self) -> Dt {
830        Dt::now().add(self)
831    }
832}