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