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: exactly **2000-01-01 12:00:00 TAI**.
9 ///
10 /// [`Dt::new(0)`].
11 pub const ZERO: Self = Self::new(0, Scale::TAI, Scale::TAI);
12
13 /// NTP epoch.
14 /// - 1900-01-01 00:00:00 UTC.
15 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
16 /// - -3_155_716_800_000_000_000_000_000_000 attoseconds
17 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
18 pub const NTP_EPOCH: Self =
19 Self::new(-3155716800000000000000000000i128, Scale::TAI, Scale::TAI);
20
21 /// UNIX epoch.
22 /// - 1970-01-01 00:00:00 TAI.
23 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
24 /// - -946_728_000_000_000_000_000_000_000 attoseconds
25 /// - Does not take into account historical UTC offsets from the "rubber time" era.
26 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
27 pub const UNIX_EPOCH: Self = Self::new(
28 -(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON as i128) * ATTOS_PER_SEC_I128,
29 Scale::TAI,
30 Scale::UTC,
31 );
32
33 /// TT/TCG/TCB/TDB epoch.
34 /// - 1977-01-01 00:00:00 TAI.
35 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
36 /// - -725_803_200_000_000_000_000_000_000 attoseconds
37 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
38 pub const TAI_1977_EPOCH: Self =
39 Self::new(-725803200000000000000000000i128, Scale::TAI, Scale::TAI);
40
41 /// Chandra X-ray Center (CXC) Time epoch.
42 /// - 1998-01-01 00:00:00 TT.
43 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
44 /// - -63_115_232_184_000_000_000_000_000_000 attoseconds
45 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
46 pub const CXC_EPOCH: Self = Self::new(-63115232184000000000000000i128, Scale::TAI, Scale::TT);
47
48 /// GPS/Galileo Experiment (GALEX) Time epoch.
49 /// - 1980-01-06 00:00:00 UTC.
50 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
51 /// - -630_763_181_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 GPS_EPOCH: Self = Self::new(-630763181000000000000000000i128, Scale::TAI, Scale::GPS);
54
55 /// Galileo System Time (GST) epoch.
56 /// - 1999-08-22 00:00:00 GST.
57 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
58 /// - -11_447_981_000_000_000_000_000_000 attoseconds
59 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
60 pub const GALILEO_EPOCH: Self =
61 Self::new(-11447981000000000000000000i128, Scale::TAI, Scale::GST);
62
63 /// BeiDou Time (BDT) epoch.
64 /// - 2006-01-01 00:00:00 UTC.
65 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
66 /// - 189_345_633_000_000_000_000_000_000 attoseconds
67 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
68 pub const BDT_EPOCH: Self = Self::new(189345633000000000000000000i128, Scale::TAI, Scale::BDT);
69
70 /// CCSDS epoch (used in CCSDS time codes such as CUC).
71 /// - 1958-01-01 00:00:00 TAI.
72 /// - Stored here on the **TAI** timescale as an offset from [`Self::ZERO`].
73 /// - -1_325_419_200_000_000_000_000_000_000 attoseconds
74 /// - The library's epoch for time scales during conversions is 2000-01-01 12:00:00.
75 pub const CCSDS_EPOCH: Self = Self::new(
76 -1_325_419_200_000_000_000_000_000_000i128,
77 Scale::TAI,
78 Scale::TAI,
79 );
80
81 /// Maximum representable duration.
82 pub const MAX: Self = Self::new(i128::MAX, Scale::TAI, Scale::TAI);
83
84 /// Minimum (most negative) representable duration.
85 pub const MIN: Self = Self::new(i128::MIN, Scale::TAI, Scale::TAI);
86
87 pub const SEC_19: Self = Self::new(19i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
88 pub const SEC_33: Self = Self::new(33i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
89 pub const SEC_37: Self = Self::new(37i128 * ATTOS_PER_SEC_I128, Scale::TAI, Scale::TAI);
90 pub const ONE_DAY: Self = Self::new(
91 (SEC_PER_DAYI64 as i128) * ATTOS_PER_SEC_I128,
92 Scale::TAI,
93 Scale::TAI,
94 );
95
96 /// Creates a new `Dt` from a total number of attoseconds (signed i128).
97 #[inline(always)]
98 pub const fn new(attos: i128, scale: Scale, target: Scale) -> Dt {
99 Self {
100 attos,
101 scale,
102 target,
103 }
104 }
105
106 /// Creates a new [`Dt`] from a total number of attoseconds (signed i128) without
107 /// performing any time scale conversions.
108 ///
109 /// This is an easy way to create a duration.
110 #[inline(always)]
111 pub const fn span(attos: i128) -> Dt {
112 Dt::new(attos, Scale::TAI, Scale::TAI)
113 }
114
115 /// Creates a [`Dt`] from a floating-point number of seconds without performing
116 /// any time scale conversions.
117 ///
118 /// This is an easy way to create a duration.
119 #[inline(always)]
120 pub const fn span_f(sec_f: Real) -> Dt {
121 Self::from_sec_f_on(sec_f, Scale::TAI)
122 }
123
124 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
125 /// the given `scale`.
126 ///
127 /// - Requires a seconds and attoseconds count such that would be returned from the
128 /// functions [`Dt::to_sec64`](../struct.Dt.html#method.to_sec64) and
129 /// [`Dt::to_sec_ufrac`](../struct.Dt.html#method.to_sec_ufrac).
130 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
131 /// the given `scale` arg.
132 /// - The `sec` should be from the epoch TAI 2000-01-01 12:00:00.
133 #[inline(always)]
134 pub(crate) fn from_sec_and_attos(sec: i64, attos: u64, scale: Scale) -> Dt {
135 if attos == 0 {
136 Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128, scale)
137 } else {
138 Dt::from_attos((sec as i128) * ATTOS_PER_SEC_I128 + (attos as i128), scale)
139 }
140 }
141
142 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
143 /// the given `scale`.
144 ///
145 /// - Requires a total attoseconds value.
146 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
147 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
148 /// the given `scale` arg.
149 #[inline(always)]
150 pub const fn from_attos(attos: i128, current: Scale) -> Dt {
151 Dt::new(attos, current, current).to_tai()
152 }
153
154 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
155 /// the given `scale`.
156 ///
157 /// - Requires a total attoseconds value.
158 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
159 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
160 /// the given `scale` arg.
161 #[inline(always)]
162 pub const fn from_attos_with_target(attos: i128, current: Scale, target: Scale) -> Dt {
163 Dt::new(attos, current, target).to_tai()
164 }
165
166 /// Creates a new [`Dt`] from a total number of seconds (signed i128) without
167 /// performing any time scale conversions.
168 #[inline(always)]
169 pub const fn from_tai_sec(sec: i128) -> Dt {
170 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), Scale::TAI)
171 }
172
173 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
174 /// the given `scale`.
175 ///
176 /// - Requires a total seconds value.
177 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
178 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
179 /// the given `scale` arg.
180 #[inline(always)]
181 pub const fn from_sec(sec: i128, scale: Scale) -> Dt {
182 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), scale)
183 }
184
185 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
186 /// the given `scale`.
187 ///
188 /// - Requires a total milliseconds value.
189 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
190 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
191 /// the given `scale` arg.
192 #[inline(always)]
193 pub const fn from_ms(ms: i128, scale: Scale) -> Dt {
194 let attos = ms.saturating_mul(ATTOS_PER_MS as i128);
195 Self::from_attos(attos, scale)
196 }
197
198 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
199 /// the given `scale`.
200 ///
201 /// - Requires a total microseconds value.
202 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
203 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
204 /// the given `scale` arg.
205 #[inline(always)]
206 pub const fn from_us(us: i128, scale: Scale) -> Dt {
207 let attos = us.saturating_mul(ATTOS_PER_US as i128);
208 Self::from_attos(attos, scale)
209 }
210
211 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
212 /// the given `scale`.
213 ///
214 /// - Requires a total nanoseconds value.
215 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
216 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
217 /// the given `scale` arg.
218 #[inline(always)]
219 pub const fn from_ns(ns: i128, scale: Scale) -> Dt {
220 let attos = ns.saturating_mul(ATTOS_PER_NS as i128);
221 Self::from_attos(attos, scale)
222 }
223
224 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
225 /// the given `scale`.
226 ///
227 /// - Requires a total picoseconds value.
228 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
229 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
230 /// the given `scale` arg.
231 #[inline(always)]
232 pub const fn from_ps(ps: i128, scale: Scale) -> Dt {
233 let attos = ps.saturating_mul(ATTOS_PER_PS as i128);
234 Self::from_attos(attos, scale)
235 }
236
237 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
238 /// the given `scale`.
239 ///
240 /// - Requires a total femtoseconds value.
241 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
242 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
243 /// the given `scale` arg.
244 #[inline(always)]
245 pub const fn from_fs(fs: i128, scale: Scale) -> Dt {
246 let attos = fs.saturating_mul(ATTOS_PER_FS as i128);
247 Self::from_attos(attos, scale)
248 }
249
250 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
251 /// the given `scale`.
252 ///
253 /// Convenience wrapper around
254 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
255 #[inline(always)]
256 pub const fn from_min(m: i64, scale: Scale) -> Dt {
257 Self::from_sec((m as i128) * 60, scale)
258 }
259
260 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
261 /// the given `scale`.
262 ///
263 /// Convenience wrapper around
264 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
265 #[inline(always)]
266 pub const fn from_hr(h: i64, scale: Scale) -> Dt {
267 Self::from_sec((h as i128) * 3600, scale)
268 }
269
270 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
271 /// the given `scale`.
272 ///
273 /// - Params are hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.
274 /// - All values are essentially optional (you can use 0 for ones you want to leave out).
275 /// - Negative values are handled.
276 /// - Uses saturating arithmetic.
277 pub const fn from_hms(
278 hr: i64,
279 min: i64,
280 sec: i64,
281 ms: i128,
282 us: i128,
283 ns: i128,
284 scale: Scale,
285 ) -> Dt {
286 // Combine hours/minutes/seconds with saturating arithmetic
287 let total_sec: i128 = (hr as i128)
288 .saturating_mul(3600)
289 .saturating_add((min as i128).saturating_mul(60))
290 .saturating_add(sec as i128);
291
292 // Combine sub-second parts (nanoseconds) with saturating arithmetic
293 let sub_ns: i128 = ms
294 .saturating_mul(1_000_000)
295 .saturating_add(us.saturating_mul(1_000))
296 .saturating_add(ns);
297
298 if sub_ns == 0 {
299 return Self::from_sec(total_sec, scale);
300 }
301
302 // Handle carry/borrow from sub-second component
303 let abs_ns: u128 = sub_ns.unsigned_abs();
304 let extra_sec: i128 = (abs_ns / 1_000_000_000) as i128;
305 let rem_ns: u64 = (abs_ns % 1_000_000_000) as u64;
306 let frac_attos: u128 = (rem_ns as u128) * (ATTOS_PER_NS as u128);
307
308 let attos = if sub_ns >= 0 {
309 total_sec
310 .saturating_add(extra_sec)
311 .saturating_mul(ATTOS_PER_SEC_I128)
312 .saturating_add(frac_attos as i128)
313 } else if frac_attos == 0 {
314 total_sec
315 .saturating_sub(extra_sec)
316 .saturating_mul(ATTOS_PER_SEC_I128)
317 } else {
318 total_sec
319 .saturating_sub(extra_sec)
320 .saturating_sub(1)
321 .saturating_mul(ATTOS_PER_SEC_I128)
322 .saturating_add(ATTOS_PER_SEC_I128 - frac_attos as i128)
323 };
324
325 Self::from_attos(attos, scale)
326 }
327
328 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
329 /// the given `scale`.
330 ///
331 /// - Convenience wrapper around
332 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
333 /// - Uses `86400` seconds per day in the calculation.
334 #[inline(always)]
335 pub const fn from_days(d: i64, scale: Scale) -> Dt {
336 Self::from_sec((d as i128).saturating_mul(SEC_PER_DAYI128), scale)
337 }
338
339 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
340 /// the given `scale`.
341 ///
342 /// - Convenience wrapper around
343 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
344 /// - Uses `604800` seconds per week in the calculation.
345 #[inline(always)]
346 pub const fn from_wk(wk: i64, scale: Scale) -> Dt {
347 Dt::from_sec((wk as i128).saturating_mul(SEC_PER_WEEK as i128), scale)
348 }
349
350 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
351 /// the given `scale`.
352 ///
353 /// - Convenience wrapper around
354 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
355 /// - Uses `31_557_600` in the calculation.
356 #[inline(always)]
357 pub const fn from_yr(yr: i64, scale: Scale) -> Dt {
358 Dt::from_sec((yr as i128).saturating_mul(31_557_600), scale)
359 }
360
361 /// Returns a [`Dt`] that is this duration ago from the given scale.
362 #[inline(always)]
363 pub const fn ago(self, scale: Scale) -> Dt {
364 Dt::from_attos(0, scale).sub(self)
365 }
366
367 /// Returns a [`Dt`] that is this duration from now in the given scale.
368 #[inline(always)]
369 pub const fn from_now(self, scale: Scale) -> Dt {
370 Dt::from_attos(0, scale).add(self)
371 }
372
373 /// Returns the negation of this [`Dt`].
374 #[inline(always)]
375 pub const fn neg(self) -> Dt {
376 Dt::new(-self.attos, self.scale, self.target)
377 }
378
379 /// Returns the positive of this [`Dt`].
380 #[inline(always)]
381 pub const fn abs(self) -> Dt {
382 Dt::new(self.attos.saturating_abs(), self.scale, self.target)
383 }
384
385 /// Creates a [`Dt`] from a floating-point number of seconds.
386 #[inline(always)]
387 pub const fn from_sec_f(sec_f: Real, scale: Scale) -> Dt {
388 Self::from_sec_f_on(sec_f, scale)
389 }
390
391 /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
392 /// Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
393 /// Returns the rounded integer (round-to-nearest, ties away from zero).
394 pub const fn sec_f_to_total_attos(sec_f: Real) -> i128 {
395 if sec_f == 0.0 {
396 return 0;
397 }
398
399 let bits = sec_f.to_bits();
400 let is_negative = (bits >> 63) != 0;
401 let biased_exp = ((bits >> 52) & 0x7ff) as i32;
402 let mantissa = bits & 0x000f_ffff_ffff_ffff;
403
404 let (sig, exp) = if biased_exp == 0 {
405 if mantissa == 0 {
406 return 0;
407 }
408 (mantissa as u128, -1022i32 - 52)
409 } else {
410 let sig = ((1u64 << 52) | mantissa) as u128;
411 (sig, biased_exp - 1023 - 52)
412 };
413
414 const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
415 let product = sig * FIVE_POW_18;
416 let total_exp = exp + 18;
417
418 // Safe saturation / underflow guards (prevents invalid shifts >= 128)
419 if total_exp > 120 {
420 return if is_negative { i128::MIN } else { i128::MAX };
421 }
422 if total_exp < -97 {
423 return 0;
424 }
425
426 let abs_total = if total_exp >= 0 {
427 let shift = total_exp as u32;
428 if product > (u128::MAX >> shift) {
429 if is_negative { i128::MIN } else { i128::MAX }
430 } else {
431 let shifted = product << shift;
432 if shifted > i128::MAX as u128 {
433 if is_negative { i128::MIN } else { i128::MAX }
434 } else {
435 shifted as i128
436 }
437 }
438 } else {
439 let shift = (-total_exp) as u32;
440 let int_part = (product >> shift) as i128;
441
442 // Round to nearest, half away from zero (on the absolute value)
443 let mask = (1u128 << shift) - 1;
444 let rem = product & mask;
445 if rem > (mask >> 1) {
446 int_part + 1
447 } else {
448 int_part
449 }
450 };
451
452 if is_negative { -abs_total } else { abs_total }
453 }
454
455 /// Creates a [`Dt`] from a floating-point number of seconds.
456 /// - Assumes the value is on the given scale.
457 /// - Converts the values **to TAI**, the returned [`Dt`] is on
458 /// the TAI time scale.
459 pub const fn from_sec_f_on(sec_f: Real, s: Scale) -> Dt {
460 if sec_f.is_nan() {
461 return Self::ZERO;
462 } else if sec_f.is_infinite() {
463 return if sec_f.is_sign_positive() {
464 Self::MAX
465 } else {
466 Self::MIN
467 };
468 }
469
470 let total_attos = Self::sec_f_to_total_attos(sec_f);
471
472 Self::from_attos(total_attos, s)
473 }
474
475 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
476 ///
477 /// This method is only available when the `std` feature is enabled and the target
478 /// is not WASM with the `js` feature.
479 #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
480 pub fn now() -> Dt {
481 let now = std::time::SystemTime::now();
482
483 let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
484 Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
485 Err(e) => {
486 let dur = e.duration();
487 (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
488 }
489 };
490
491 Dt::from_diff_and_scale(
492 Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
493 Dt::UNIX_EPOCH,
494 false,
495 )
496 .add(Dt::from_ns(nanos as i128, Scale::TAI))
497 }
498
499 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
500 /// (browser WASM version using JavaScript’s `Date.now()`).
501 #[cfg(all(target_arch = "wasm32", feature = "js"))]
502 pub fn now() -> Dt {
503 let ms: f64 = js_sys::Date::now();
504 let secs = (ms / 1000.0).floor() as i128;
505 let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
506 Dt::from_diff_and_scale(
507 Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
508 Dt::UNIX_EPOCH,
509 false,
510 )
511 .add(Dt::from_ns(nanos as i128, Scale::TAI))
512 }
513}