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