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