1use crate::historical_sofa::{historical_sofa_for_tai_to_utc, historical_sofa_for_utc_to_tai};
2use crate::leap_seconds::get_leap_seconds;
3use crate::{
4 ATTOS_PER_SEC, ATTOS_PER_SEC_I128, ClockDrift, ClockModel, Dt, J2000_JD_TT,
5 J2000_SEC_PER_CENTURY, LB_DEN, LB_NUM, LG_DEN, LG_NUM, LM_DEN, LM_NUM, MARS_MSD_REF_JD_INT,
6 MARS_MSD_REF_TOD_SEC, MARS_MSD_REF_TOD_SUBSEC, MARS_REF_TT, MARS_SOL_ATTOS,
7 MARS_SOL_LENGTH_SEC, Real, SEC_PER_DAYI64, SEC_PER_DAYI128, Scale, TAI_SEC_AT_1972,
8 TCG_TCB_REF_JD_INT, TCG_TCB_REF_TOD_SEC, TCG_TCB_REF_TOD_SUBSEC, TDB0_ATTOS, TSpan,
9 TT_TAI_OFFSET_SPAN, floor_f, sin_approx, to_sec_f,
10};
11
12impl Dt {
13 #[inline(always)]
14 pub const fn to_span(&self) -> TSpan {
15 TSpan {
16 sec: self.sec,
17 attos: self.attos,
18 }
19 }
20
21 pub const fn from(sec: i64, attos: u64, scale: Scale) -> Dt {
22 let raw = Dt::new(sec, attos);
24
25 match scale {
26 Scale::TAI | Scale::Custom | Scale::UT1 => raw,
27
28 Scale::TT => raw.sub(TT_TAI_OFFSET_SPAN),
29
30 Scale::UTC => raw.add(TSpan::from_sec(get_leap_seconds(&raw, true).offset)),
31
32 Scale::UTCSpice => {
33 let tai = raw.add(TSpan::from_sec(get_leap_seconds(&raw, true).offset));
34 if sec < TAI_SEC_AT_1972 - 10 {
35 tai.add(TSpan::from_sec_f(f!(9.0)))
36 } else {
37 tai
38 }
39 }
40 Scale::UTCSofa => {
41 let tai = raw.add(TSpan::from_sec(get_leap_seconds(&raw, true).offset));
42 if let Some(offset) = historical_sofa_for_utc_to_tai(&raw) {
43 tai.add(TSpan::from_sec_f(offset))
44 } else {
45 tai
46 }
47 }
48
49 Scale::GPS | Scale::QZSS | Scale::GST => raw.add(TSpan::SEC_19),
50
51 Scale::BDT => raw.add(TSpan::SEC_33),
52
53 Scale::TDB | Scale::ET => Self::tdb_to_tai(raw),
54
55 Scale::TCG => {
56 let tt = Self::tcg_to_tt(raw);
57 tt.sub(TT_TAI_OFFSET_SPAN)
58 }
59
60 Scale::TCB => {
61 let tdb = Self::tcb_to_tdb(raw);
62 Self::tdb_to_tai(tdb)
63 }
64
65 Scale::LTC => {
66 let tt = Self::ltc_to_tt(raw);
67 tt.sub(TT_TAI_OFFSET_SPAN)
68 }
69 }
70 }
71
72 pub const fn to(&self, scale: Scale) -> TSpan {
78 match scale {
79 Scale::TAI | Scale::Custom | Scale::UT1 => self.to_span(),
80
81 Scale::TT => self.add(TT_TAI_OFFSET_SPAN).to_span(),
82
83 Scale::UTC => self
84 .sub(TSpan::from_sec(get_leap_seconds(&self, false).offset))
85 .to_span(),
86
87 Scale::UTCSpice => {
88 if self.sec < TAI_SEC_AT_1972 {
89 self.sub(TSpan::from_sec(get_leap_seconds(&self, false).offset))
90 .sub(TSpan::from_sec_f(f!(9.0)))
91 .to_span()
92 } else {
93 self.sub(TSpan::from_sec(get_leap_seconds(&self, false).offset))
94 .to_span()
95 }
96 }
97 Scale::UTCSofa => {
98 if let Some(offset) = historical_sofa_for_tai_to_utc(&self) {
99 self.sub(TSpan::from_sec(get_leap_seconds(&self, false).offset))
100 .sub(TSpan::from_sec_f(offset))
101 .to_span()
102 } else {
103 self.sub(TSpan::from_sec(get_leap_seconds(&self, false).offset))
104 .to_span()
105 }
106 }
107
108 Scale::GPS | Scale::QZSS | Scale::GST => self.sub(TSpan::SEC_19).to_span(),
109
110 Scale::BDT => self.sub(TSpan::SEC_33).to_span(),
111
112 Scale::TDB | Scale::ET => Self::tai_to_tdb(*self).to_span(),
113
114 Scale::TCG => Self::tai_to_tcg(*self).to_span(),
115
116 Scale::TCB => Self::tai_to_tcb(*self).to_span(),
117
118 Scale::LTC => {
119 let tt = self.add(TT_TAI_OFFSET_SPAN);
120 Self::tt_to_ltc(tt).to_span()
121 }
122 }
123 }
124
125 #[inline]
128 pub const fn convert_using_drift(self, reference: Self, drift: ClockDrift) -> Self {
129 let span = self.to_tai_since(reference);
130 let correction = drift.time_diff_after(&span);
131 self.add(correction)
132 }
133
134 pub const fn convert_back_using_drift(self, reference: Self, drift: ClockDrift) -> Self {
140 if drift.rate().is_zero() && drift.accel().is_zero() {
141 return self.sub(*drift.constant());
142 }
143 let mut guess = self;
144 let mut i = 0u32;
145 while i < 16 {
146 let span = guess.to_tai_since(reference);
147 let correction = drift.time_diff_after(&span);
148 guess = self.sub(correction);
149 i += 1;
150 }
151 guess
152 }
153
154 #[inline]
159 pub const fn convert_using_model(self, model: ClockModel) -> Self {
160 self.convert_using_drift(model.reference, model.drift)
161 }
162
163 #[inline]
165 pub const fn convert_back_using_model(self, model: ClockModel) -> Self {
166 self.convert_back_using_drift(model.reference, model.drift)
167 }
168
169 const fn tdb_minus_tt(sec: i64, attos: u64) -> TSpan {
216 let seconds_since_j2000_tt = f!(sec) + f!(attos) / f!(ATTOS_PER_SEC);
217 let t = seconds_since_j2000_tt / J2000_SEC_PER_CENTURY;
218
219 let g =
221 f!(2.0) * core::f64::consts::PI * (f!(357.52910918) + f!(35999.050290) * t) / f!(360.0);
222
223 let sin_g = sin_approx(g + f!(0.01671) * sin_approx(g));
225
226 let sin_2g = sin_approx(f!(2.0) * g);
228
229 let lunar = sin_approx(f!(52.9690965) * t + f!(0.444401603));
231
232 let venus = sin_approx(f!(606.977675) * t + f!(4.021195093));
234
235 let correction = f!(0.001656674564) * sin_g
236 + f!(0.000022417471) * sin_2g
237 + f!(0.000004770086) * lunar
238 + f!(0.000004676740) * venus;
239
240 TSpan::from_sec_f(correction)
241 }
242
243 const fn tai_to_tdb(tai: Self) -> Self {
244 let tt = tai.add(TT_TAI_OFFSET_SPAN);
245 let span = Self::tdb_minus_tt(tt.sec, tt.attos);
246 tt.add(span)
247 }
248
249 const fn tdb_to_tai(tdb: Self) -> Self {
250 let mut tt = tdb;
251 let mut i = 0u32;
252 while i < 8 {
253 tt = tdb.sub(Self::tdb_minus_tt(tt.sec, tt.attos));
254 i += 1;
255 }
256 tt.sub(TT_TAI_OFFSET_SPAN)
257 }
258
259 const fn tai_to_tcg(tai: Self) -> Self {
260 let tt = tai.add(TT_TAI_OFFSET_SPAN);
261 Self::tt_to_tcg(tt)
262 }
263
264 const fn tai_to_tcb(tai: Self) -> Self {
265 let tdb = Self::tai_to_tdb(tai);
266 Self::tdb_to_tcb(tdb)
267 }
268
269 const fn elapsed_to_attos_since_ref(numerical: Self) -> i128 {
272 let days_since_j2000 = numerical.sec.div_euclid(SEC_PER_DAYI64);
273 let tod_sec = numerical.sec.rem_euclid(SEC_PER_DAYI64);
274
275 let jd_days = J2000_JD_TT + days_since_j2000;
276 let days_diff = jd_days - TCG_TCB_REF_JD_INT;
277
278 let mut sec_diff =
279 (days_diff as i128) * SEC_PER_DAYI128 + (tod_sec as i128 - TCG_TCB_REF_TOD_SEC as i128);
280 let mut attos_diff = (numerical.attos as i128) - (TCG_TCB_REF_TOD_SUBSEC as i128);
281
282 if attos_diff < 0 {
283 attos_diff += ATTOS_PER_SEC_I128;
284 sec_diff -= 1;
285 }
286
287 sec_diff * ATTOS_PER_SEC_I128 + attos_diff
288 }
289
290 const fn mul_rate(attos: i128, num: i128, den: i128) -> i128 {
292 if attos == 0 {
293 return 0;
294 }
295 let sign = if attos < 0 { -1i128 } else { 1i128 };
296 let a = if attos < 0 { -attos } else { attos };
297 let q = a / den;
298 let r = a % den;
299 sign * (q * num + (r * num) / den)
300 }
301
302 #[inline]
303 const fn mul_lg(attos: i128) -> i128 {
304 Self::mul_rate(attos, LG_NUM, LG_DEN)
305 }
306
307 #[inline]
308 const fn mul_lb(attos: i128) -> i128 {
309 Self::mul_rate(attos, LB_NUM, LB_DEN)
310 }
311
312 #[inline]
313 const fn mul_lm(attos: i128) -> i128 {
314 Self::mul_rate(attos, LM_NUM, LM_DEN)
315 }
316
317 const fn tt_to_tcg(tt: Self) -> Self {
318 let elapsed = Self::elapsed_to_attos_since_ref(tt);
319 let span_attos = Self::mul_lg(elapsed);
320 tt.add(TSpan::from_attos(span_attos))
321 }
322
323 const fn tcg_to_tt(tcg: Self) -> Self {
324 let elapsed_cg = Self::elapsed_to_attos_since_ref(tcg);
325 let span_attos = Self::mul_rate(elapsed_cg, LG_NUM, LG_DEN + LG_NUM);
326 tcg.sub(TSpan::from_attos(span_attos))
327 }
328
329 const fn tcb_to_tdb(tcb: Self) -> Self {
330 let elapsed_cg = Self::elapsed_to_attos_since_ref(tcb);
331 let span_attos = Self::mul_rate(elapsed_cg, LB_NUM, LB_DEN + LB_NUM);
332 tcb.sub(TSpan::from_attos(span_attos))
333 .sub(TSpan::from_attos(TDB0_ATTOS))
334 }
335
336 const fn tdb_to_tcb(tdb: Self) -> Self {
337 let elapsed = Self::elapsed_to_attos_since_ref(tdb);
338 let span_attos = Self::mul_lb(elapsed);
339 tdb.add(TSpan::from_attos(span_attos))
340 .add(TSpan::from_attos(TDB0_ATTOS))
341 }
342
343 const fn tt_to_ltc(tt: Self) -> Self {
344 let elapsed = Self::elapsed_to_attos_since_ref(tt);
345 let span_attos = Self::mul_lm(elapsed);
346 tt.add(TSpan::from_attos(span_attos))
347 }
348
349 const fn ltc_to_tt(ltc: Self) -> Self {
350 let elapsed = Self::elapsed_to_attos_since_ref(ltc);
351 let span_attos = Self::mul_rate(elapsed, LM_NUM, LM_DEN + LM_NUM);
352 ltc.sub(TSpan::from_attos(span_attos))
353 }
354
355 const fn elapsed_to_attos_since_mars_ref(numerical_tt: TSpan) -> i128 {
357 let days_since_j2000 = numerical_tt.sec.div_euclid(SEC_PER_DAYI64);
358 let tod_sec = numerical_tt.sec.rem_euclid(SEC_PER_DAYI64);
359
360 let jd_days = J2000_JD_TT + days_since_j2000;
361 let days_diff = jd_days - MARS_MSD_REF_JD_INT;
362
363 let mut sec_diff = (days_diff as i128) * SEC_PER_DAYI128
364 + (tod_sec as i128 - MARS_MSD_REF_TOD_SEC as i128);
365 let mut attos_diff = (numerical_tt.attos as i128) - (MARS_MSD_REF_TOD_SUBSEC as i128);
366
367 if attos_diff < 0 {
368 attos_diff += ATTOS_PER_SEC_I128;
369 sec_diff -= 1;
370 }
371
372 sec_diff * ATTOS_PER_SEC_I128 + attos_diff
373 }
374
375 pub const fn to_msd_exact(self) -> (i64, u128) {
380 let tt = self.to(Scale::TT);
381 let elapsed = Self::elapsed_to_attos_since_mars_ref(tt);
382 let attos_per_sol = MARS_SOL_ATTOS;
383
384 let whole_sols = elapsed.div_euclid(attos_per_sol) as i64;
385 let frac_attos = elapsed.rem_euclid(attos_per_sol) as u128;
386
387 (whole_sols, frac_attos)
388 }
389
390 #[inline]
393 pub const fn to_mtc(self) -> TSpan {
394 let (_, frac_attos) = self.to_msd_exact();
395 TSpan::from_attos(frac_attos as i128)
396 }
397
398 pub const fn from_msd_exact(whole_sols: i64, frac_attos: u128) -> Self {
400 let elapsed_attos = (whole_sols as i128) * MARS_SOL_ATTOS + frac_attos as i128;
401
402 let tt = MARS_REF_TT.add(TSpan::from_attos(elapsed_attos));
403 Self::from(tt.sec, tt.attos, Scale::TT)
404 }
405
406 pub const fn from_msd(msd: Real) -> Self {
409 let whole = floor_f(msd) as i64;
410 let frac = msd - f!(whole);
411 let frac_span = TSpan::from_sec_f(frac * MARS_SOL_LENGTH_SEC);
412 Self::from_msd_exact(whole, frac_span.to_attos() as u128)
413 }
414
415 #[inline]
418 pub const fn to_msd(self) -> Real {
419 let (whole, frac) = self.to_msd_exact();
420 f!(whole) + to_sec_f(frac) / MARS_SOL_LENGTH_SEC
421 }
422}