deep_time/dt/constructors.rs
1use crate::{
2 ATTOS_PER_FS, ATTOS_PER_MS, ATTOS_PER_NS, ATTOS_PER_PS, ATTOS_PER_SEC_I128, ATTOS_PER_US, Dt,
3 Real, SEC_PER_DAYI64, SEC_PER_DAYI128, SEC_PER_WEEK, Scale,
4 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_DAYI64 as i128) * ATTOS_PER_SEC_I128,
110 Scale::TAI,
111 Scale::TAI,
112 );
113
114 /// Creates a new [`Dt`] from a total number of attoseconds since the librarys
115 /// epoch **2000-01-01 12:00:00 TAI**.
116 ///
117 /// Does **not** perform any time scale conversions.
118 ///
119 /// ## Examples
120 ///
121 /// ```rust
122 /// use deep_time::{Dt, Scale};
123 ///
124 /// // current scale TAI, target scale UTC
125 /// let a = Dt::new(0, Scale::TAI, Scale::UTC);
126 ///
127 /// // equivalent to direct construction
128 /// let b = Dt { attos: 0, scale: Scale::TAI, target: Scale::UTC };
129 ///
130 /// assert_eq!(a, b);
131 /// ```
132 #[inline(always)]
133 pub const fn new(attos: i128, scale: Scale, target: Scale) -> Dt {
134 Self {
135 attos,
136 scale,
137 target,
138 }
139 }
140
141 /// Creates a new [`Dt`] from a total number of seconds since the librarys
142 /// epoch **2000-01-01 12:00:00 TAI**.
143 ///
144 /// Does **not** perform any time scale conversions.
145 #[inline(always)]
146 pub const fn new_sec(sec: i128, scale: Scale, target: Scale) -> Dt {
147 Self {
148 attos: Dt::sec_to_attos(sec),
149 scale,
150 target,
151 }
152 }
153
154 /// Creates a new [`Dt`] from a total number of seconds as a float
155 /// since the librarys epoch **2000-01-01 12:00:00 TAI**.
156 ///
157 /// - Does **not** perform any time scale conversions.
158 /// - Fractional seconds represented by any decimals.
159 #[inline(always)]
160 pub const fn new_f(sec: Real, scale: Scale, target: Scale) -> Dt {
161 Self {
162 attos: Dt::sec_f_to_attos(sec),
163 scale,
164 target,
165 }
166 }
167
168 /// Creates a new [`Dt`] from a total number of attoseconds (signed i128) without
169 /// performing any time scale conversions.
170 ///
171 /// - This is an easy way to create a duration.
172 /// - The returned [`Dt`] has its `scale` and `target` fields set to
173 /// `Scale::TAI`.
174 #[inline(always)]
175 pub const fn span(attos: i128) -> Dt {
176 Dt::new(attos, Scale::TAI, Scale::TAI)
177 }
178
179 /// Creates a [`Dt`] from a floating-point number of seconds without performing
180 /// any time scale conversions.
181 ///
182 /// - This is an easy way to create a duration or a seconds count that doesn't
183 /// include any time scale conversions, just holds the seconds count as is.
184 /// - The returned [`Dt`] has its `scale` and `target` fields set to
185 /// `Scale::TAI`.
186 #[inline(always)]
187 pub const fn span_f(sec: Real) -> Dt {
188 Self::from_sec_f(sec, Scale::TAI)
189 }
190
191 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
192 /// the given `scale`.
193 ///
194 /// - **Requires** a seconds and attoseconds count such that would be returned from the
195 /// functions [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64) and
196 /// **[`Dt::to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac)**.
197 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
198 /// the given `scale` arg.
199 /// - The `sec` should be from the epoch TAI 2000-01-01 12:00:00.
200 ///
201 /// This function performs a time scale conversion from the given `scale` to **TAI**,
202 /// if you don't want any time scale conversion to take place then either use
203 /// `Scale::TAI` as an arg or use any of the following constructors:
204 ///
205 /// - [`Dt::new`](../struct.Dt.html#method.new)
206 /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
207 /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
208 /// - [`Dt::span`](../struct.Dt.html#method.span)
209 /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
210 /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
211 #[inline(always)]
212 pub fn from_sec_and_ufrac(sec: i64, attos: u64, scale: Scale) -> Dt {
213 if attos == 0 {
214 Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128, scale)
215 } else {
216 Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128 + (attos as i128), scale)
217 }
218 }
219
220 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
221 /// the given `scale`.
222 ///
223 /// - **Requires** a seconds and attoseconds count such that would be returned from the
224 /// functions [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64) and
225 /// **[`Dt::to_sec_frac`](../struct.Dt.html#method.to_sec_frac)**.
226 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
227 /// the given `scale` arg.
228 /// - The `sec` should be from the epoch TAI 2000-01-01 12:00:00.
229 ///
230 /// This function performs a time scale conversion from the given `scale` to **TAI**,
231 /// if you don't want any time scale conversion to take place then either use
232 /// `Scale::TAI` as an arg or use any of the following constructors:
233 ///
234 /// - [`Dt::new`](../struct.Dt.html#method.new)
235 /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
236 /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
237 /// - [`Dt::span`](../struct.Dt.html#method.span)
238 /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
239 /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
240 #[inline(always)]
241 pub fn from_sec_and_attos(sec: i64, attos: u64, scale: Scale) -> Dt {
242 let attos = Dt::sec_and_attos_to_attos(sec, attos);
243 Dt::from_attos(attos, scale)
244 }
245
246 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
247 /// the given `scale`.
248 ///
249 /// - Requires a total attoseconds value.
250 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
251 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
252 /// the given `scale` arg.
253 ///
254 /// This function performs a time scale conversion from the given `scale` to **TAI**,
255 /// if you don't want any time scale conversion to take place then either use
256 /// `Scale::TAI` as an arg or use any of the following constructors:
257 ///
258 /// - [`Dt::new`](../struct.Dt.html#method.new)
259 /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
260 /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
261 /// - [`Dt::span`](../struct.Dt.html#method.span)
262 /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
263 /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
264 #[inline(always)]
265 pub const fn from_attos(attos: i128, scale: Scale) -> Dt {
266 Dt::new(attos, scale, scale).to_tai()
267 }
268
269 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
270 /// the given `scale`.
271 ///
272 /// - Requires a total attoseconds value.
273 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
274 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
275 /// the given `scale` arg.
276 ///
277 /// This function performs a time scale conversion from the given `scale` to **TAI**,
278 /// if you don't want any time scale conversion to take place then either use
279 /// `Scale::TAI` as an arg or use any of the following constructors:
280 ///
281 /// - [`Dt::new`](../struct.Dt.html#method.new)
282 /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
283 /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
284 /// - [`Dt::span`](../struct.Dt.html#method.span)
285 /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
286 /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
287 #[inline(always)]
288 pub const fn from_attos_with_target(attos: i128, scale: Scale, target: Scale) -> Dt {
289 Dt::new(attos, scale, target).to_tai()
290 }
291
292 /// Creates a new [`Dt`] from a total number of seconds (signed i128) without
293 /// performing any time scale conversions.
294 #[inline(always)]
295 pub const fn from_tai_sec(sec: i128) -> Dt {
296 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), Scale::TAI)
297 }
298
299 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
300 /// the given `scale`.
301 ///
302 /// - Requires a total seconds value.
303 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
304 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
305 /// the given `scale` arg.
306 ///
307 /// This function performs a time scale conversion from the given `scale` to **TAI**,
308 /// if you don't want any time scale conversion to take place then either use
309 /// `Scale::TAI` as an arg or use any of the following constructors:
310 ///
311 /// - [`Dt::new`](../struct.Dt.html#method.new)
312 /// - [`Dt::new_sec`](../struct.Dt.html#method.new_sec)
313 /// - [`Dt::new_f`](../struct.Dt.html#method.new_f)
314 /// - [`Dt::span`](../struct.Dt.html#method.span)
315 /// - [`Dt::span_f`](../struct.Dt.html#method.span_f)
316 /// - [`Dt::from_tai_sec`](../struct.Dt.html#method.from_tai_sec)
317 #[inline(always)]
318 pub const fn from_sec(sec: i128, scale: Scale) -> Dt {
319 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), scale)
320 }
321
322 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
323 /// the given `scale`.
324 ///
325 /// - Requires a total milliseconds value.
326 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
327 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
328 /// the given `scale` arg.
329 #[inline(always)]
330 pub const fn from_ms(ms: i128, scale: Scale) -> Dt {
331 let attos = ms.saturating_mul(ATTOS_PER_MS as i128);
332 Self::from_attos(attos, scale)
333 }
334
335 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
336 /// the given `scale`.
337 ///
338 /// - Requires a total microseconds value.
339 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
340 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
341 /// the given `scale` arg.
342 #[inline(always)]
343 pub const fn from_us(us: i128, scale: Scale) -> Dt {
344 let attos = us.saturating_mul(ATTOS_PER_US as i128);
345 Self::from_attos(attos, scale)
346 }
347
348 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
349 /// the given `scale`.
350 ///
351 /// - Requires a total nanoseconds value.
352 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
353 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
354 /// the given `scale` arg.
355 #[inline(always)]
356 pub const fn from_ns(ns: i128, scale: Scale) -> Dt {
357 let attos = ns.saturating_mul(ATTOS_PER_NS as i128);
358 Self::from_attos(attos, scale)
359 }
360
361 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
362 /// the given `scale`.
363 ///
364 /// - Requires a total picoseconds value.
365 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
366 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
367 /// the given `scale` arg.
368 #[inline(always)]
369 pub const fn from_ps(ps: i128, scale: Scale) -> Dt {
370 let attos = ps.saturating_mul(ATTOS_PER_PS as i128);
371 Self::from_attos(attos, scale)
372 }
373
374 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
375 /// the given `scale`.
376 ///
377 /// - Requires a total femtoseconds value.
378 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
379 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
380 /// the given `scale` arg.
381 #[inline(always)]
382 pub const fn from_fs(fs: i128, scale: Scale) -> Dt {
383 let attos = fs.saturating_mul(ATTOS_PER_FS as i128);
384 Self::from_attos(attos, scale)
385 }
386
387 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
388 /// the given `scale`.
389 ///
390 /// Convenience wrapper around
391 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
392 #[inline(always)]
393 pub const fn from_min(m: i64, scale: Scale) -> Dt {
394 Self::from_sec((m as i128) * 60, scale)
395 }
396
397 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
398 /// the given `scale`.
399 ///
400 /// Convenience wrapper around
401 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
402 #[inline(always)]
403 pub const fn from_hr(h: i64, scale: Scale) -> Dt {
404 Self::from_sec((h as i128) * 3600, scale)
405 }
406
407 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
408 /// the given `scale`.
409 ///
410 /// - Params are hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.
411 /// - All values are essentially optional (you can use 0 for ones you want to leave out).
412 /// - Negative values are handled.
413 /// - Uses saturating arithmetic.
414 pub const fn from_hms(
415 hr: i64,
416 min: i64,
417 sec: i64,
418 ms: i128,
419 us: i128,
420 ns: i128,
421 scale: Scale,
422 ) -> Dt {
423 // Combine hours/minutes/seconds with saturating arithmetic
424 let total_sec: i128 = (hr as i128)
425 .saturating_mul(3600)
426 .saturating_add((min as i128).saturating_mul(60))
427 .saturating_add(sec as i128);
428
429 // Combine sub-second parts (nanoseconds) with saturating arithmetic
430 let sub_ns: i128 = ms
431 .saturating_mul(1_000_000)
432 .saturating_add(us.saturating_mul(1_000))
433 .saturating_add(ns);
434
435 if sub_ns == 0 {
436 return Self::from_sec(total_sec, scale);
437 }
438
439 // Handle carry/borrow from sub-second component
440 let abs_ns: u128 = sub_ns.unsigned_abs();
441 let extra_sec: i128 = (abs_ns / 1_000_000_000) as i128;
442 let rem_ns: u64 = (abs_ns % 1_000_000_000) as u64;
443 let frac_attos: u128 = (rem_ns as u128) * (ATTOS_PER_NS as u128);
444
445 let attos = if sub_ns >= 0 {
446 total_sec
447 .saturating_add(extra_sec)
448 .saturating_mul(ATTOS_PER_SEC_I128)
449 .saturating_add(frac_attos as i128)
450 } else if frac_attos == 0 {
451 total_sec
452 .saturating_sub(extra_sec)
453 .saturating_mul(ATTOS_PER_SEC_I128)
454 } else {
455 total_sec
456 .saturating_sub(extra_sec)
457 .saturating_sub(1)
458 .saturating_mul(ATTOS_PER_SEC_I128)
459 .saturating_add(ATTOS_PER_SEC_I128 - frac_attos as i128)
460 };
461
462 Self::from_attos(attos, scale)
463 }
464
465 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
466 /// the given `scale`.
467 ///
468 /// - Convenience wrapper around
469 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
470 /// - Uses `86400` seconds per day in the calculation.
471 #[inline(always)]
472 pub const fn from_days(d: i64, scale: Scale) -> Dt {
473 Self::from_sec((d as i128).saturating_mul(SEC_PER_DAYI128), scale)
474 }
475
476 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
477 /// the given `scale`.
478 ///
479 /// - Convenience wrapper around
480 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
481 /// - Uses `604800` seconds per week in the calculation.
482 #[inline(always)]
483 pub const fn from_wk(wk: i64, scale: Scale) -> Dt {
484 Dt::from_sec((wk as i128).saturating_mul(SEC_PER_WEEK as i128), scale)
485 }
486
487 /// Returns a [`Dt`] on the TAI time scale, after having been **converted** to TAI from
488 /// the given `scale`.
489 ///
490 /// - Convenience wrapper around
491 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
492 /// - Uses `31_557_600` in the calculation.
493 #[inline(always)]
494 pub const fn from_yr(yr: i64, scale: Scale) -> Dt {
495 Dt::from_sec((yr as i128).saturating_mul(31_557_600), scale)
496 }
497
498 /// Returns a [`Dt`] that is this duration ago from the given scale.
499 #[inline(always)]
500 pub const fn ago(self, scale: Scale) -> Dt {
501 Dt::from_attos(0, scale).sub(self)
502 }
503
504 /// Returns a [`Dt`] that is this duration from now in the given scale.
505 #[inline(always)]
506 pub const fn from_now(self, scale: Scale) -> Dt {
507 Dt::from_attos(0, scale).add(self)
508 }
509
510 /// Returns the negation of this [`Dt`].
511 #[inline(always)]
512 pub const fn neg(self) -> Dt {
513 Dt::new(-self.attos, self.scale, self.target)
514 }
515
516 /// Returns the positive of this [`Dt`].
517 #[inline(always)]
518 pub const fn abs(self) -> Dt {
519 Dt::new(self.attos.saturating_abs(), self.scale, self.target)
520 }
521
522 /// Creates a [`Dt`] from a floating-point number of seconds.
523 ///
524 /// - Assumes the value is on the given scale.
525 /// - Converts the values to TAI from the given `scale`.
526 /// - The returned [`Dt`] is on the TAI time scale.
527 #[inline]
528 pub const fn from_sec_f(sec: Real, scale: Scale) -> Dt {
529 if sec.is_nan() {
530 return Self::ZERO;
531 } else if sec.is_infinite() {
532 return if sec.is_sign_positive() {
533 Self::MAX
534 } else {
535 Self::MIN
536 };
537 }
538 Self::from_attos(Self::sec_f_to_attos(sec), scale)
539 }
540
541 /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
542 ///
543 /// - Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
544 /// - Returns the rounded integer (round-to-nearest, ties away from zero).
545 pub const fn sec_f_to_attos(sec: Real) -> i128 {
546 if sec == 0.0 {
547 return 0;
548 }
549
550 let bits = sec.to_bits();
551 let is_negative = (bits >> 63) != 0;
552 let biased_exp = ((bits >> 52) & 0x7ff) as i32;
553 let mantissa = bits & 0x000f_ffff_ffff_ffff;
554
555 let (sig, exp) = if biased_exp == 0 {
556 if mantissa == 0 {
557 return 0;
558 }
559 (mantissa as u128, -1022i32 - 52)
560 } else {
561 let sig = ((1u64 << 52) | mantissa) as u128;
562 (sig, biased_exp - 1023 - 52)
563 };
564
565 const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
566 let product = sig * FIVE_POW_18;
567 let total_exp = exp + 18;
568
569 // Safe saturation / underflow guards (prevents invalid shifts >= 128)
570 if total_exp > 120 {
571 return if is_negative { i128::MIN } else { i128::MAX };
572 }
573 if total_exp < -97 {
574 return 0;
575 }
576
577 let abs_total = if total_exp >= 0 {
578 let shift = total_exp as u32;
579 if product > (u128::MAX >> shift) {
580 if is_negative { i128::MIN } else { i128::MAX }
581 } else {
582 let shifted = product << shift;
583 if shifted > i128::MAX as u128 {
584 if is_negative { i128::MIN } else { i128::MAX }
585 } else {
586 shifted as i128
587 }
588 }
589 } else {
590 let shift = (-total_exp) as u32;
591 let int_part = (product >> shift) as i128;
592
593 // Round to nearest, half away from zero (on the absolute value)
594 let mask = (1u128 << shift) - 1;
595 let rem = product & mask;
596 if rem > (mask >> 1) {
597 int_part + 1
598 } else {
599 int_part
600 }
601 };
602
603 if is_negative { -abs_total } else { abs_total }
604 }
605
606 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
607 ///
608 /// This method is only available when the `std` feature is enabled and the target
609 /// is not WASM with the `js` feature.
610 #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
611 pub fn now() -> Dt {
612 let now = std::time::SystemTime::now();
613
614 let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
615 Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
616 Err(e) => {
617 let dur = e.duration();
618 (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
619 }
620 };
621
622 Dt::from_diff_and_scale(
623 Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
624 Dt::UNIX_EPOCH,
625 false,
626 )
627 .add(Dt::from_ns(nanos as i128, Scale::TAI))
628 }
629
630 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
631 /// (browser WASM version using JavaScript’s `Date.now()`).
632 #[cfg(all(target_arch = "wasm32", feature = "js"))]
633 pub fn now() -> Dt {
634 let ms: f64 = js_sys::Date::now();
635 let secs = (ms / 1000.0).floor() as i128;
636 let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
637 Dt::from_diff_and_scale(
638 Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
639 Dt::UNIX_EPOCH,
640 false,
641 )
642 .add(Dt::from_ns(nanos as i128, Scale::TAI))
643 }
644}