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 /// Creates a new [`Dt`] from a total number of seconds (signed i128) without
155 /// performing any time scale conversions.
156 #[inline(always)]
157 pub const fn from_tai_sec(sec: i128) -> Dt {
158 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), Scale::TAI)
159 }
160
161 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
162 /// the given `scale`.
163 ///
164 /// - Requires a total seconds value.
165 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
166 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
167 /// the given `scale` arg.
168 #[inline(always)]
169 pub const fn from_sec(sec: i128, scale: Scale) -> Dt {
170 Self::from_attos(sec.saturating_mul(ATTOS_PER_SEC_I128), scale)
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 milliseconds 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_ms(ms: i128, scale: Scale) -> Dt {
182 let attos = ms.saturating_mul(ATTOS_PER_MS as i128);
183 Self::from_attos(attos, scale)
184 }
185
186 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
187 /// the given `scale`.
188 ///
189 /// - Requires a total microseconds value.
190 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
191 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
192 /// the given `scale` arg.
193 #[inline(always)]
194 pub const fn from_us(us: i128, scale: Scale) -> Dt {
195 let attos = us.saturating_mul(ATTOS_PER_US as i128);
196 Self::from_attos(attos, scale)
197 }
198
199 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
200 /// the given `scale`.
201 ///
202 /// - Requires a total nanoseconds value.
203 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
204 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
205 /// the given `scale` arg.
206 #[inline(always)]
207 pub const fn from_ns(ns: i128, scale: Scale) -> Dt {
208 let attos = ns.saturating_mul(ATTOS_PER_NS as i128);
209 Self::from_attos(attos, scale)
210 }
211
212 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
213 /// the given `scale`.
214 ///
215 /// - Requires a total picoseconds value.
216 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
217 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
218 /// the given `scale` arg.
219 #[inline(always)]
220 pub const fn from_ps(ps: i128, scale: Scale) -> Dt {
221 let attos = ps.saturating_mul(ATTOS_PER_PS as i128);
222 Self::from_attos(attos, scale)
223 }
224
225 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
226 /// the given `scale`.
227 ///
228 /// - Requires a total femtoseconds value.
229 /// - The value should be from the epoch TAI 2000-01-01 12:00:00.
230 /// - The returned object's `scale` field is set to TAI and its `target` field is set to
231 /// the given `scale` arg.
232 #[inline(always)]
233 pub const fn from_fs(fs: i128, scale: Scale) -> Dt {
234 let attos = fs.saturating_mul(ATTOS_PER_FS as i128);
235 Self::from_attos(attos, scale)
236 }
237
238 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
239 /// the given `scale`.
240 ///
241 /// Convenience wrapper around
242 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
243 #[inline(always)]
244 pub const fn from_min(m: i64, scale: Scale) -> Dt {
245 Self::from_sec((m as i128) * 60, scale)
246 }
247
248 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
249 /// the given `scale`.
250 ///
251 /// Convenience wrapper around
252 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
253 #[inline(always)]
254 pub const fn from_hr(h: i64, scale: Scale) -> Dt {
255 Self::from_sec((h as i128) * 3600, scale)
256 }
257
258 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
259 /// the given `scale`.
260 ///
261 /// - Params are hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.
262 /// - All values are essentially optional (you can use 0 for ones you want to leave out).
263 /// - Negative values are handled.
264 /// - Uses saturating arithmetic.
265 pub const fn from_hms(
266 hr: i64,
267 min: i64,
268 sec: i64,
269 ms: i128,
270 us: i128,
271 ns: i128,
272 scale: Scale,
273 ) -> Dt {
274 // Combine hours/minutes/seconds with saturating arithmetic
275 let total_sec: i128 = (hr as i128)
276 .saturating_mul(3600)
277 .saturating_add((min as i128).saturating_mul(60))
278 .saturating_add(sec as i128);
279
280 // Combine sub-second parts (nanoseconds) with saturating arithmetic
281 let sub_ns: i128 = ms
282 .saturating_mul(1_000_000)
283 .saturating_add(us.saturating_mul(1_000))
284 .saturating_add(ns);
285
286 if sub_ns == 0 {
287 return Self::from_sec(total_sec, scale);
288 }
289
290 // Handle carry/borrow from sub-second component
291 let abs_ns: u128 = sub_ns.unsigned_abs();
292 let extra_sec: i128 = (abs_ns / 1_000_000_000) as i128;
293 let rem_ns: u64 = (abs_ns % 1_000_000_000) as u64;
294 let frac_attos: u128 = (rem_ns as u128) * (ATTOS_PER_NS as u128);
295
296 let attos = if sub_ns >= 0 {
297 total_sec
298 .saturating_add(extra_sec)
299 .saturating_mul(ATTOS_PER_SEC_I128)
300 .saturating_add(frac_attos as i128)
301 } else if frac_attos == 0 {
302 total_sec
303 .saturating_sub(extra_sec)
304 .saturating_mul(ATTOS_PER_SEC_I128)
305 } else {
306 total_sec
307 .saturating_sub(extra_sec)
308 .saturating_sub(1)
309 .saturating_mul(ATTOS_PER_SEC_I128)
310 .saturating_add(ATTOS_PER_SEC_I128 - frac_attos as i128)
311 };
312
313 Self::from_attos(attos, scale)
314 }
315
316 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
317 /// the given `scale`.
318 ///
319 /// - Convenience wrapper around
320 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
321 /// - Uses `86400` seconds per day in the calculation.
322 #[inline(always)]
323 pub const fn from_days(d: i64, scale: Scale) -> Dt {
324 Self::from_sec((d as i128).saturating_mul(SEC_PER_DAYI128), scale)
325 }
326
327 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
328 /// the given `scale`.
329 ///
330 /// - Convenience wrapper around
331 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
332 /// - Uses `604800` seconds per week in the calculation.
333 #[inline(always)]
334 pub const fn from_wk(wk: i64, scale: Scale) -> Dt {
335 Dt::from_sec((wk as i128).saturating_mul(SEC_PER_WEEK as i128), scale)
336 }
337
338 /// Returns a [`Dt`] on the TAI time scale, after having been converted to TAI from
339 /// the given `scale`.
340 ///
341 /// - Convenience wrapper around
342 /// [`Dt::from_sec`](../struct.Dt.html#method.from_sec).
343 /// - Uses `31_557_600` in the calculation.
344 #[inline(always)]
345 pub const fn from_yr(yr: i64, scale: Scale) -> Dt {
346 Dt::from_sec((yr as i128).saturating_mul(31_557_600), scale)
347 }
348
349 /// Returns a [`Dt`] that is this duration ago from the given scale.
350 #[inline(always)]
351 pub const fn ago(self, scale: Scale) -> Dt {
352 Dt::from_attos(0, scale).sub(self)
353 }
354
355 /// Returns a [`Dt`] that is this duration from now in the given scale.
356 #[inline(always)]
357 pub const fn from_now(self, scale: Scale) -> Dt {
358 Dt::from_attos(0, scale).add(self)
359 }
360
361 /// Returns the negation of this [`Dt`].
362 #[inline(always)]
363 pub const fn neg(self) -> Dt {
364 Dt::new(-self.attos, self.scale, self.target)
365 }
366
367 /// Returns the positive of this [`Dt`].
368 #[inline(always)]
369 pub const fn abs(self) -> Dt {
370 Dt::new(self.attos.saturating_abs(), self.scale, self.target)
371 }
372
373 /// Creates a [`Dt`] from a floating-point number of seconds.
374 #[inline(always)]
375 pub const fn from_sec_f(sec_f: Real, scale: Scale) -> Dt {
376 Self::from_sec_f_on(sec_f, scale)
377 }
378
379 /// High-precision conversion from [`Real`] seconds to total attoseconds (i128).
380 /// Uses IEEE 754 bit extraction + exact integer multiplication by 5^18.
381 /// Returns the rounded integer (round-to-nearest, ties away from zero).
382 pub const fn sec_f_to_total_attos(sec_f: Real) -> i128 {
383 if sec_f == 0.0 {
384 return 0;
385 }
386
387 let bits = sec_f.to_bits();
388 let is_negative = (bits >> 63) != 0;
389 let biased_exp = ((bits >> 52) & 0x7ff) as i32;
390 let mantissa = bits & 0x000f_ffff_ffff_ffff;
391
392 let (sig, exp) = if biased_exp == 0 {
393 if mantissa == 0 {
394 return 0;
395 }
396 (mantissa as u128, -1022i32 - 52)
397 } else {
398 let sig = ((1u64 << 52) | mantissa) as u128;
399 (sig, biased_exp - 1023 - 52)
400 };
401
402 const FIVE_POW_18: u128 = 3_814_697_265_625; // 5^18 exactly
403 let product = sig * FIVE_POW_18;
404 let total_exp = exp + 18;
405
406 // Safe saturation / underflow guards (prevents invalid shifts >= 128)
407 if total_exp > 120 {
408 return if is_negative { i128::MIN } else { i128::MAX };
409 }
410 if total_exp < -97 {
411 return 0;
412 }
413
414 let abs_total = if total_exp >= 0 {
415 let shift = total_exp as u32;
416 if product > (u128::MAX >> shift) {
417 if is_negative { i128::MIN } else { i128::MAX }
418 } else {
419 let shifted = product << shift;
420 if shifted > i128::MAX as u128 {
421 if is_negative { i128::MIN } else { i128::MAX }
422 } else {
423 shifted as i128
424 }
425 }
426 } else {
427 let shift = (-total_exp) as u32;
428 let int_part = (product >> shift) as i128;
429
430 // Round to nearest, half away from zero (on the absolute value)
431 let mask = (1u128 << shift) - 1;
432 let rem = product & mask;
433 if rem > (mask >> 1) {
434 int_part + 1
435 } else {
436 int_part
437 }
438 };
439
440 if is_negative { -abs_total } else { abs_total }
441 }
442
443 /// Creates a [`Dt`] from a floating-point number of seconds.
444 /// - Assumes the value is on the given scale.
445 /// - Converts the values **to TAI**, the returned [`Dt`] is on
446 /// the TAI time scale.
447 pub const fn from_sec_f_on(sec_f: Real, s: Scale) -> Dt {
448 if sec_f.is_nan() {
449 return Self::ZERO;
450 } else if sec_f.is_infinite() {
451 return if sec_f.is_sign_positive() {
452 Self::MAX
453 } else {
454 Self::MIN
455 };
456 }
457
458 let total_attos = Self::sec_f_to_total_attos(sec_f);
459
460 Self::from_attos(total_attos, s)
461 }
462
463 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
464 ///
465 /// This method is only available when the `std` feature is enabled and the target
466 /// is not WASM with the `js` feature.
467 #[cfg(all(feature = "std", not(all(target_arch = "wasm32", feature = "js"))))]
468 pub fn now() -> Dt {
469 let now = std::time::SystemTime::now();
470
471 let (secs, nanos): (i64, i64) = match now.duration_since(std::time::UNIX_EPOCH) {
472 Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos() as i64),
473 Err(e) => {
474 let dur = e.duration();
475 (-(dur.as_secs() as i64), -(dur.subsec_nanos() as i64))
476 }
477 };
478
479 Dt::from_diff_and_scale(
480 Dt::new(Dt::sec_to_attos(secs as i128), Scale::TAI, Scale::UTC),
481 Dt::UNIX_EPOCH,
482 false,
483 )
484 .add(Dt::from_ns(nanos as i128, Scale::TAI))
485 }
486
487 /// Returns the current system time as TAI from 2000-01-01 12:00:00.
488 /// (browser WASM version using JavaScript’s `Date.now()`).
489 #[cfg(all(target_arch = "wasm32", feature = "js"))]
490 pub fn now() -> Dt {
491 let ms: f64 = js_sys::Date::now();
492 let secs = (ms / 1000.0).floor() as i128;
493 let nanos = ((ms % 1000.0) * 1_000_000.0) as i128;
494 Dt::from_diff_and_scale(
495 Dt::new(Dt::sec_to_attos(secs), Scale::TAI, Scale::UTC),
496 Dt::UNIX_EPOCH,
497 false,
498 )
499 .add(Dt::from_ns(nanos as i128, Scale::TAI))
500 }
501}