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        Self {
135            attos,
136            scale,
137            target,
138        }
139    }
140
141    /// Creates a new [`Dt`] from a total number of seconds since the librarys
142    /// epoch **2000-01-01 12:00:00 TAI**.
143    ///
144    /// Does **not** perform any time scale conversions.
145    #[inline(always)]
146    pub const fn new_sec(sec: i128, scale: Scale, target: Scale) -> Dt {
147        Self {
148            attos: Dt::sec_to_attos(sec),
149            scale,
150            target,
151        }
152    }
153
154    /// Creates a new [`Dt`] from a total number of seconds as a float
155    /// since the librarys epoch **2000-01-01 12:00:00 TAI**.
156    ///
157    /// - Does **not** perform any time scale conversions.
158    /// - Fractional seconds represented by any decimals.
159    #[inline(always)]
160    pub const fn new_f(sec: Real, scale: Scale, target: Scale) -> Dt {
161        Self {
162            attos: Dt::sec_f_to_attos(sec),
163            scale,
164            target,
165        }
166    }
167
168    /// Creates a new [`Dt`] from a total number of attoseconds (signed i128) without
169    /// performing any time scale conversions.
170    ///
171    /// - This is an easy way to create a duration.
172    /// - The returned [`Dt`] has its `scale` and `target` fields set to
173    ///   `Scale::TAI`.
174    #[inline(always)]
175    pub const fn span(attos: i128) -> Dt {
176        Dt::new(attos, Scale::TAI, Scale::TAI)
177    }
178
179    /// Creates a [`Dt`] from a floating-point number of seconds without performing
180    /// any time scale conversions.
181    ///
182    /// - This is an easy way to create a duration or a seconds count that doesn't
183    ///   include any time scale conversions, just holds the seconds count as is.
184    /// - The returned [`Dt`] has its `scale` and `target` fields set to
185    ///   `Scale::TAI`.
186    #[inline(always)]
187    pub const fn span_f(sec: Real) -> Dt {
188        Self::from_sec_f(sec, Scale::TAI)
189    }
190
191    /// 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    /// ## Examples
197    ///
198    /// ```rust
199    /// use deep_time::{Dt, Scale};
200    ///
201    /// // A leap second from the middle of the table (36 leap seconds accumulated)
202    /// let original = Dt::from_ymd(2015, 6, 30, Scale::UTC, 23, 59, 60, 123_456_789_000_000_000);
203    ///
204    /// // Round-trip through canonical attoseconds
205    /// let canon = original.to_diff_raw(Dt::UNIX_EPOCH).to_attos();
206    /// let roundtrip1 = Dt::from_diff_raw(canon, Dt::UNIX_EPOCH);
207    ///
208    /// assert_eq!(original, roundtrip1, "Canonical round-trip failed");
209    /// ```
210    #[inline]
211    pub const fn from_diff_raw(attos: i128, epoch: Dt) -> Dt {
212        epoch.add(Dt::new(attos, epoch.scale, epoch.target))
213    }
214
215    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
216    /// the given `scale`.
217    ///
218    /// - **Requires** a seconds and attoseconds count such that would be returned from the
219    ///   functions [`Dt::to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor) and
220    ///   **[`Dt::to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac)**.
221    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
222    ///   the given `scale` arg.
223    /// - The `sec` should be from the epoch TAI 2000-01-01 12:00:00.
224    ///
225    /// This function performs a time scale conversion from the given `scale` to **TAI**,
226    /// if you don't want any time scale conversion to take place then either use
227    /// `Scale::TAI` as an arg or use any of the following constructors:
228    ///
229    /// - [`Dt::new`](../struct.Dt.html#method.new)
230    /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
231    /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
232    /// - [`Dt::span`](../struct.Dt.html#method.span)
233    /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
234    /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
235    #[inline(always)]
236    pub fn from_sec_and_ufrac(sec: i64, attos: u64, scale: Scale) -> Dt {
237        if attos == 0 {
238            Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128, scale)
239        } else {
240            Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128 + (attos as i128), scale)
241        }
242    }
243
244    /// Builds a [`Dt`] from whole seconds plus a sub-second attoseconds remainder.
245    ///
246    /// - `sec` — whole seconds only (no fraction). Use
247    ///   [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64)
248    ///   to obtain this from an existing [`Dt`].
249    /// - `attos` — the signed sub-second remainder in attoseconds, as returned by
250    ///   [`Dt::to_sec_frac`](../struct.Dt.html#method.to_sec_frac).
251    ///   For a total of `1.3` s: `sec = 1`, `attos = 300_000_000_000_000_000`.
252    ///   For `-1.3` s: `sec = -1`, `attos = -300_000_000_000_000_000`.
253    ///   For `-0.5` s: `sec = 0`, `attos = -500_000_000_000_000_000`.
254    ///
255    /// This whole/remainder split differs from
256    /// [`Dt::to_sec64_floor`](../struct.Dt.html#method.to_sec64_floor)
257    /// +
258    /// [`Dt::to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
259    /// Use
260    /// [`from_sec_and_ufrac`](../struct.Dt.html#method.from_sec_and_ufrac)
261    /// for that pairing.
262    ///
263    /// ## Examples
264    ///
265    /// ```rust
266    /// use deep_time::{Dt, Scale};
267    ///
268    /// let dt = Dt::span(1_300_000_000_000_000_000);
269    /// assert_eq!(
270    ///     Dt::from_sec_and_frac(1, 300_000_000_000_000_000, Scale::TAI),
271    ///     dt,
272    /// );
273    ///
274    /// let dt = Dt::span(-1_300_000_000_000_000_000);
275    /// assert_eq!(
276    ///     Dt::from_sec_and_frac(-1, -300_000_000_000_000_000, Scale::TAI),
277    ///     dt,
278    /// );
279    ///
280    /// let dt = Dt::span(-500_000_000_000_000_000);
281    /// assert_eq!(
282    ///     Dt::from_sec_and_frac(0, -500_000_000_000_000_000, Scale::TAI),
283    ///     dt,
284    /// );
285    /// ```
286    ///
287    /// The result is stored on TAI and converted from `scale`.
288    /// `sec` is measured from the library epoch: 2000-01-01 12:00:00 TAI.
289    ///
290    /// To avoid scale conversion, pass `Scale::TAI`, or use one of:
291    ///
292    /// - [`Dt::new`](../struct.Dt.html#method.new)
293    /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
294    /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
295    /// - [`Dt::span`](../struct.Dt.html#method.span)
296    /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
297    /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
298    #[inline(always)]
299    pub fn from_sec_and_frac(sec: i64, attos: i64, scale: Scale) -> Dt {
300        Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128 + (attos as i128), scale)
301    }
302
303    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
304    /// the given `scale`.
305    ///
306    /// - Requires a total attoseconds value.
307    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
308    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
309    ///   the given `scale` arg.
310    ///
311    /// This function performs a time scale conversion from the given `scale` to **TAI**,
312    /// if you don't want any time scale conversion to take place then either use
313    /// `Scale::TAI` as an arg or use any of the following constructors:
314    ///
315    /// - [`Dt::new`](../struct.Dt.html#method.new)
316    /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
317    /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
318    /// - [`Dt::span`](../struct.Dt.html#method.span)
319    /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
320    /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
321    #[inline(always)]
322    pub const fn from_attos(attos: i128, scale: Scale) -> Dt {
323        Dt::new(attos, scale, scale).to_tai()
324    }
325
326    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
327    /// the given `scale`.
328    ///
329    /// - Requires a total attoseconds value.
330    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
331    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
332    ///   the given `scale` arg.
333    ///
334    /// This function performs a time scale conversion from the given `scale` to **TAI**,
335    /// if you don't want any time scale conversion to take place then either use
336    /// `Scale::TAI` as an arg or use any of the following constructors:
337    ///
338    /// - [`Dt::new`](../struct.Dt.html#method.new)
339    /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
340    /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
341    /// - [`Dt::span`](../struct.Dt.html#method.span)
342    /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
343    /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
344    #[inline(always)]
345    pub const fn from_attos_with_target(attos: i128, scale: Scale, target: Scale) -> Dt {
346        Dt::new(attos, scale, target).to_tai()
347    }
348
349    /// Creates a new [`Dt`] from a total number of seconds (signed i128) without
350    /// performing any time scale conversions.
351    #[inline(always)]
352    pub const fn from_tai_sec(sec: i128) -> Dt {
353        Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), Scale::TAI)
354    }
355
356    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
357    /// the given `scale`.
358    ///
359    /// - Requires a total seconds value.
360    /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
361    /// - The returned object's `scale` field is set to TAI and its `target` field is set to
362    ///   the given `scale` arg.
363    ///
364    /// This function performs a time scale conversion from the given `scale` to **TAI**,
365    /// if you don't want any time scale conversion to take place then either use
366    /// `Scale::TAI` as an arg or use any of the following constructors:
367    ///
368    /// - [`Dt::new`](../struct.Dt.html#method.new)
369    /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
370    /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
371    /// - [`Dt::span`](../struct.Dt.html#method.span)
372    /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
373    /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
374    #[inline(always)]
375    pub const fn from_sec(sec: i128, scale: Scale) -> Dt {
376        Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), scale)
377    }
378
379    /// Builds a [`Dt`] from whole milliseconds plus a non-negative fractional part in attoseconds.
380    ///
381    /// Pairs with [`to_ms_floor`](../struct.Dt.html#method.to_ms_floor): the fractional part is
382    /// always **added**, even when `ms` is negative (Euclidean / floor split).
383    ///
384    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
385    /// - The result is stored on TAI after conversion from `scale`.
386    #[inline(always)]
387    pub const fn from_ms_floor(ms: i128, frac_attos: u128, scale: Scale) -> Dt {
388        let attos = Dt::unit_and_attos_to_attos(ms, frac_attos, ATTOS_PER_MS_I128);
389        Self::from_attos(attos, scale)
390    }
391
392    /// Builds a [`Dt`] from whole microseconds plus a non-negative fractional part in attoseconds.
393    ///
394    /// Pairs with [`to_us_floor`](../struct.Dt.html#method.to_us_floor): the fractional part is
395    /// always **added**, even when `us` is negative (Euclidean / floor split).
396    ///
397    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
398    /// - The result is stored on TAI after conversion from `scale`.
399    #[inline(always)]
400    pub const fn from_us_floor(us: i128, frac_attos: u128, scale: Scale) -> Dt {
401        let attos = Dt::unit_and_attos_to_attos(us, frac_attos, ATTOS_PER_US_I128);
402        Self::from_attos(attos, scale)
403    }
404
405    /// Builds a [`Dt`] from whole nanoseconds plus a non-negative fractional part in attoseconds.
406    ///
407    /// Pairs with [`to_ns_floor`](../struct.Dt.html#method.to_ns_floor): the fractional part is
408    /// always **added**, even when `ns` is negative (Euclidean / floor split).
409    ///
410    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
411    /// - The result is stored on TAI after conversion from `scale`.
412    #[inline(always)]
413    pub const fn from_ns_floor(ns: i128, frac_attos: u128, scale: Scale) -> Dt {
414        let attos = Dt::unit_and_attos_to_attos(ns, frac_attos, ATTOS_PER_NS_I128);
415        Self::from_attos(attos, scale)
416    }
417
418    /// Builds a [`Dt`] from whole picoseconds plus a non-negative fractional part in attoseconds.
419    ///
420    /// Pairs with [`to_ps_floor`](../struct.Dt.html#method.to_ps_floor): the fractional part is
421    /// always **added**, even when `ps` is negative (Euclidean / floor split).
422    ///
423    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
424    /// - The result is stored on TAI after conversion from `scale`.
425    #[inline(always)]
426    pub const fn from_ps_floor(ps: i128, frac_attos: u128, scale: Scale) -> Dt {
427        let attos = Dt::unit_and_attos_to_attos(ps, frac_attos, ATTOS_PER_PS_I128);
428        Self::from_attos(attos, scale)
429    }
430
431    /// Builds a [`Dt`] from whole femtoseconds plus a non-negative fractional part in attoseconds.
432    ///
433    /// Pairs with [`to_fs_floor`](../struct.Dt.html#method.to_fs_floor): the fractional part is
434    /// always **added**, even when `fs` is negative (Euclidean / floor split).
435    ///
436    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
437    /// - The result is stored on TAI after conversion from `scale`.
438    #[inline(always)]
439    pub const fn from_fs_floor(fs: i128, frac_attos: u128, scale: Scale) -> Dt {
440        let attos = Dt::unit_and_attos_to_attos(fs, frac_attos, ATTOS_PER_FS_I128);
441        Self::from_attos(attos, scale)
442    }
443
444    /// Builds a [`Dt`] from whole minutes plus a non-negative fractional part in attoseconds.
445    ///
446    /// Pairs with [`to_mins_floor`](../struct.Dt.html#method.to_mins_floor): the fractional part is
447    /// always **added**, even when `m` is negative (Euclidean / floor split).
448    ///
449    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
450    /// - The result is stored on TAI after conversion from `scale`.
451    #[inline(always)]
452    pub const fn from_mins_floor(n: i128, frac_attos: u128, scale: Scale) -> Dt {
453        let attos = Dt::unit_and_attos_to_attos(n, frac_attos, ATTOS_PER_MIN);
454        Self::from_attos(attos, scale)
455    }
456
457    /// Builds a [`Dt`] from whole hours plus a non-negative fractional part in attoseconds.
458    ///
459    /// Pairs with [`to_hours_floor`](../struct.Dt.html#method.to_hours_floor): the fractional part is
460    /// always **added**, even when `h` is negative (Euclidean / floor split).
461    ///
462    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
463    /// - The result is stored on TAI after conversion from `scale`.
464    #[inline(always)]
465    pub const fn from_hours_floor(n: i128, frac_attos: u128, scale: Scale) -> Dt {
466        let attos = Dt::unit_and_attos_to_attos(n, frac_attos, ATTOS_PER_HOUR);
467        Self::from_attos(attos, scale)
468    }
469
470    /// Builds a [`Dt`] from whole days plus a non-negative fractional part in attoseconds.
471    ///
472    /// Pairs with [`to_days_floor`](../struct.Dt.html#method.to_days_floor): the fractional part is
473    /// always **added**, even when `d` is negative (Euclidean / floor split).
474    ///
475    /// - Uses `86400` seconds per day.
476    /// - Values are measured from the epoch TAI 2000-01-01 12:00:00.
477    /// - The result is stored on TAI after conversion from `scale`.
478    #[inline(always)]
479    pub const fn from_days_floor(d: i128, frac_attos: u128, scale: Scale) -> Dt {
480        let attos = Dt::unit_and_attos_to_attos(d, frac_attos, ATTOS_PER_DAY);
481        Self::from_attos(attos, scale)
482    }
483
484    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
485    /// the given `scale`.
486    ///
487    /// - Convenience wrapper around
488    ///   [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
489    /// - Uses `604800` seconds per week in the calculation.
490    #[inline(always)]
491    pub const fn from_weeks(n: i128, scale: Scale) -> Dt {
492        Dt::from_sec(n.saturating_mul(SEC_PER_WEEK as i128), scale)
493    }
494
495    /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
496    /// the given `scale`.
497    ///
498    /// - Convenience wrapper around
499    ///   [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
500    /// - Uses `31_557_600` in the calculation.
501    #[inline(always)]
502    pub const fn from_years(n: i128, scale: Scale) -> Dt {
503        Dt::from_sec(n.saturating_mul(31_557_600), scale)
504    }
505
506    /// Returns an instant that is this duration **before** zero attoseconds on `scale`.
507    ///
508    /// Zero attoseconds is the library epoch **2000-01-01 12:00:00** (see
509    /// [`Dt::ZERO`](../struct.Dt.html#associatedconstant.ZERO)). This method is `const` and
510    /// does **not** read the system clock.
511    ///
512    /// For wall-clock “N units ago”, use [`Dt::ago`](../struct.Dt.html#method.ago)
513    /// (requires `std`, or WASM with `js`).
514    ///
515    /// ## Examples
516    ///
517    /// ```rust
518    /// use deep_time::{Dt, Scale, TimeTraits};
519    ///
520    /// let t = 5.sec().before_zero(Scale::TAI);
521    /// assert_eq!(t, Dt::ZERO.sub(5.sec()));
522    /// assert_eq!(t.to_sec(), -5);
523    /// ```
524    ///
525    /// ## See also
526    ///
527    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
528    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
529    /// - [`Dt::from_attos`](../struct.Dt.html#method.from_attos)
530    #[inline(always)]
531    pub const fn before_zero(self, scale: Scale) -> Dt {
532        Dt::from_attos(0, scale).sub(self)
533    }
534
535    /// Returns an instant that is this duration **after** zero attoseconds on `scale`.
536    ///
537    /// Zero attoseconds is the library epoch **2000-01-01 12:00:00** (see
538    /// [`Dt::ZERO`](../struct.Dt.html#associatedconstant.ZERO)). This method is `const` and
539    /// does **not** read the system clock.
540    ///
541    /// For wall-clock “N units from now”, use
542    /// [`Dt::from_now`](../struct.Dt.html#method.from_now) (requires `std`, or WASM with
543    /// `js`).
544    ///
545    /// ## Examples
546    ///
547    /// ```rust
548    /// use deep_time::{Dt, Scale, TimeTraits};
549    ///
550    /// let t = 5.sec().after_zero(Scale::TAI);
551    /// assert_eq!(t, Dt::ZERO.add(5.sec()));
552    /// assert_eq!(t.to_sec(), 5);
553    /// ```
554    ///
555    /// ## See also
556    ///
557    /// - [`Dt::before_zero`](../struct.Dt.html#method.before_zero)
558    /// - [`Dt::from_now`](../struct.Dt.html#method.from_now)
559    /// - [`Dt::from_attos`](../struct.Dt.html#method.from_attos)
560    #[inline(always)]
561    pub const fn after_zero(self, scale: Scale) -> Dt {
562        Dt::from_attos(0, scale).add(self)
563    }
564
565    /// Returns the negation of this [`Dt`].
566    #[inline(always)]
567    pub const fn neg(self) -> Dt {
568        Dt::new(-self.attos, self.scale, self.target)
569    }
570
571    /// Returns the positive of this [`Dt`].
572    #[inline(always)]
573    pub const fn abs(self) -> Dt {
574        Dt::new(self.attos.saturating_abs(), self.scale, self.target)
575    }
576
577    /// Creates a [`Dt`] from a floating-point number of seconds.
578    ///
579    /// - Assumes the value is on the given scale.
580    /// - Converts the value to TAI from the given `scale`.
581    /// - The returned [`Dt`] is on the TAI time scale - its `scale`
582    ///   field is `TAI` and its `target` field is the provided time
583    ///   scale argument.
584    ///
585    /// ## Examples
586    ///
587    /// ```rust
588    /// use deep_time::{Dt, Scale};
589    ///
590    /// let seconds = 5.5;
591    ///
592    /// // use TAI for no conversions
593    /// let duration = Dt::from_sec_f(seconds, Scale::TAI);
594    ///
595    /// assert_eq!(duration.to_sec_f(), seconds);
596    /// ```
597    #[inline]
598    pub const fn from_sec_f(sec: Real, scale: Scale) -> Dt {
599        if sec.is_nan() {
600            return Self::ZERO;
601        } else if sec.is_infinite() {
602            return if sec.is_sign_positive() {
603                Self::MAX
604            } else {
605                Self::MIN
606            };
607        }
608        Self::from_attos(Self::sec_f_to_attos(sec), scale)
609    }
610
611    /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
612    ///
613    /// - Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
614    /// - Returns the rounded integer (round-to-nearest, ties away from zero).
615    pub const fn sec_f_to_attos(sec: Real) -> i128 {
616        if sec == 0.0 {
617            return 0;
618        }
619
620        let bits = sec.to_bits();
621        let is_negative = (bits >> 63) != 0;
622        let biased_exp = ((bits >> 52) & 0x7ff) as i32;
623        let mantissa = bits & 0x000f_ffff_ffff_ffff;
624
625        let (sig, exp) = if biased_exp == 0 {
626            if mantissa == 0 {
627                return 0;
628            }
629            (mantissa as u128, -1022i32 - 52)
630        } else {
631            let sig = ((1u64 << 52) | mantissa) as u128;
632            (sig, biased_exp - 1023 - 52)
633        };
634
635        const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
636        let product = sig * FIVE_POW_18;
637        let total_exp = exp + 18;
638
639        // Safe saturation / underflow guards (prevents invalid shifts >= 128)
640        if total_exp > 120 {
641            return if is_negative { i128::MIN } else { i128::MAX };
642        }
643        if total_exp < -97 {
644            return 0;
645        }
646
647        let abs_total = if total_exp >= 0 {
648            let shift = total_exp as u32;
649            if product > (u128::MAX >> shift) {
650                if is_negative { i128::MIN } else { i128::MAX }
651            } else {
652                let shifted = product << shift;
653                if shifted > i128::MAX as u128 {
654                    if is_negative { i128::MIN } else { i128::MAX }
655                } else {
656                    shifted as i128
657                }
658            }
659        } else {
660            let shift = (-total_exp) as u32;
661            let int_part = (product >> shift) as i128;
662
663            // Round to nearest, half away from zero (on the absolute value)
664            let mask = (1u128 << shift) - 1;
665            let rem = product & mask;
666            if rem > (mask >> 1) {
667                int_part + 1
668            } else {
669                int_part
670            }
671        };
672
673        if is_negative { -abs_total } else { abs_total }
674    }
675
676    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
677    ///
678    /// This method is only available when the `std` feature is enabled and the target
679    /// is not WASM with the `js` feature.
680    #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
681    pub fn now() -> Dt {
682        let now = std::time::SystemTime::now();
683
684        let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
685            Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
686            Err(e) => {
687                let dur = e.duration();
688                (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
689            }
690        };
691
692        Dt::from_diff_and_scale(
693            Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
694            Dt::UNIX_EPOCH,
695            false,
696        )
697        .add(Dt::from_ns_floor(nanos as i128, 0, Scale::TAI))
698    }
699
700    /// Returns the current system time as TAI from 2000-01-01 12:00:00.
701    /// (browser WASM version using JavaScript’s `Date.now()`).
702    #[cfg(all(target_arch = "wasm32", feature = "js"))]
703    pub fn now() -> Dt {
704        let ms: f64 = js_sys::Date::now();
705        let secs = (ms / 1000.0).floor() as i128;
706        let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
707        Dt::from_diff_and_scale(
708            Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
709            Dt::UNIX_EPOCH,
710            false,
711        )
712        .add(Dt::from_ns_floor(nanos as i128, 0, Scale::TAI))
713    }
714
715    /// Returns an instant that is this duration **before** the current system time.
716    ///
717    /// Subtracts `self` from [`Dt::now`](../struct.Dt.html#method.now). Available under
718    /// the same conditions as that method: the `std` feature (non-WASM-js), or WASM with
719    /// the `js` feature.
720    ///
721    /// For a `const` offset from the library epoch (no system clock), use
722    /// [`Dt::before_zero`](../struct.Dt.html#method.before_zero).
723    ///
724    /// ## Examples
725    ///
726    /// ```rust
727    /// # #[cfg(feature = "std")]
728    /// # {
729    /// use deep_time::{Dt, TimeTraits};
730    ///
731    /// // ~3 days in the past relative to the system clock
732    /// let past = 3.days().ago();
733    /// assert!(past < Dt::now());
734    /// # }
735    /// ```
736    ///
737    /// ## See also
738    ///
739    /// - [`Dt::from_now`](../struct.Dt.html#method.from_now)
740    /// - [`Dt::before_zero`](../struct.Dt.html#method.before_zero)
741    /// - [`Dt::now`](../struct.Dt.html#method.now)
742    #[cfg(any(
743        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
744        all(target_arch = "wasm32", feature = "js"),
745    ))]
746    #[inline]
747    pub fn ago(self) -> Dt {
748        Dt::now().sub(self)
749    }
750
751    /// Returns an instant that is this duration **after** the current system time.
752    ///
753    /// Adds `self` to [`Dt::now`](../struct.Dt.html#method.now). Available under the same
754    /// conditions as that method: the `std` feature (non-WASM-js), or WASM with the `js`
755    /// feature.
756    ///
757    /// For a `const` offset from the library epoch (no system clock), use
758    /// [`Dt::after_zero`](../struct.Dt.html#method.after_zero).
759    ///
760    /// ## Examples
761    ///
762    /// ```rust
763    /// # #[cfg(feature = "std")]
764    /// # {
765    /// use deep_time::{Dt, TimeTraits};
766    ///
767    /// // ~3 days in the future relative to the system clock
768    /// let future = 3.days().from_now();
769    /// assert!(future > Dt::now());
770    /// # }
771    /// ```
772    ///
773    /// ## See also
774    ///
775    /// - [`Dt::ago`](../struct.Dt.html#method.ago)
776    /// - [`Dt::after_zero`](../struct.Dt.html#method.after_zero)
777    /// - [`Dt::now`](../struct.Dt.html#method.now)
778    #[cfg(any(
779        all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))),
780        all(target_arch = "wasm32", feature = "js"),
781    ))]
782    #[inline]
783    pub fn from_now(self) -> Dt {
784        Dt::now().add(self)
785    }
786}