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