use super::instant::Time;
use super::scales::UT;
use super::JulianDate;
use qtty::{Days, Seconds, Simplify};
const TERMS: usize = 187;
#[rustfmt::skip]
const DELTA_T: [Seconds; TERMS] = qtty::qtty_vec!(
Seconds;
124.0,115.0,106.0, 98.0, 91.0, 85.0, 79.0, 74.0, 70.0, 65.0,
62.0, 58.0, 55.0, 53.0, 50.0, 48.0, 46.0, 44.0, 42.0, 40.0,
37.0, 35.0, 33.0, 31.0, 28.0, 26.0, 24.0, 22.0, 20.0, 18.0,
16.0, 14.0, 13.0, 12.0, 11.0, 10.0, 9.0, 9.0, 9.0, 9.0,
9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 10.0, 10.0, 10.0, 11.0,
11.0, 11.0, 11.0, 11.0, 11.0, 11.0, 12.0, 12.0, 12.0, 12.0,
12.0, 12.0, 13.0, 13.0, 13.0, 13.0, 14.0, 14.0, 14.0, 15.0,
15.0, 15.0, 15.0, 16.0, 16.0, 16.0, 16.0, 16.0, 17.0, 17.0,
17.0, 17.0, 17.0, 17.0, 17.0, 17.0, 16.0, 16.0, 15.0, 14.0,
13.7, 13.1, 12.7, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.3,
12.0, 11.4, 10.6, 9.6, 8.6, 7.5, 6.6, 6.0, 5.7, 5.6,
5.7, 5.9, 6.2, 6.5, 6.8, 7.1, 7.3, 7.5, 7.7, 7.8,
7.9, 7.5, 6.4, 5.4, 2.9, 1.6, -1.0, -2.7, -3.6, -4.7,
-5.4, -5.2, -5.5, -5.6, -5.8, -5.9, -6.2, -6.4, -6.1, -4.7,
-2.7, 0.0, 2.6, 5.4, 7.7, 10.5, 13.4, 16.0, 18.2, 20.2,
21.2, 22.4, 23.5, 23.9, 24.3, 24.0, 23.9, 23.9, 23.7, 24.0,
24.3, 25.3, 26.2, 27.3, 28.2, 29.1, 30.0, 30.7, 31.4, 32.2,
33.1, 34.0, 35.0, 36.5, 38.3, 40.2, 42.2, 44.5, 46.5, 48.5,
50.5, 52.2, 53.8, 54.9, 55.8, 56.9, 58.3,
);
const OBSERVED_TERMS: usize = 34;
const OBSERVED_START_YEAR: f64 = 1992.0;
#[rustfmt::skip]
const OBSERVED_DT: [Seconds; OBSERVED_TERMS] = qtty::qtty_vec!(
Seconds;
58.31, 59.12, 59.98, 60.78, 61.63, 62.30, 62.97, 63.47,
63.83, 64.09, 64.30, 64.47, 64.57, 64.69, 64.85, 65.15,
65.46, 65.78, 66.07, 66.32, 66.60, 66.91, 67.28, 67.64,
68.10, 68.59, 68.97, 69.22, 69.36, 69.36, 69.29, 69.18,
69.09, 69.36,
);
const OBSERVED_END_YEAR: f64 = OBSERVED_START_YEAR + OBSERVED_TERMS as f64;
const EXTRAPOLATION_RATE: f64 = 0.02;
#[inline]
fn delta_t_ancient(jd: JulianDate) -> Seconds {
const DT_A0_S: Seconds = Seconds::new(1_830.0);
const DT_A1_S: Seconds = Seconds::new(-405.0);
const DT_A2_S: Seconds = Seconds::new(46.5);
const JD_EPOCH_948_UT: JulianDate = JulianDate::new(2_067_314.5);
let c = days_ratio(jd - JD_EPOCH_948_UT, JulianDate::JULIAN_CENTURY);
DT_A0_S + DT_A1_S * c + DT_A2_S * c * c
}
#[inline]
fn delta_t_medieval(jd: JulianDate) -> Seconds {
const JD_EPOCH_1850_UT: JulianDate = JulianDate::new(2_396_758.5);
const DT_A2_S: Seconds = Seconds::new(22.5);
let c = days_ratio(jd - JD_EPOCH_1850_UT, JulianDate::JULIAN_CENTURY);
DT_A2_S * c * c
}
#[inline]
fn delta_t_table(jd: JulianDate) -> Seconds {
const JD_TABLE_START_1620: JulianDate = JulianDate::new(2_312_752.5);
const BIENNIAL_STEP_D: Days = Days::new(730.5);
let mut i = days_ratio(jd - JD_TABLE_START_1620, BIENNIAL_STEP_D) as usize;
if i > TERMS - 3 {
i = TERMS - 3;
}
let a: Seconds = DELTA_T[i + 1] - DELTA_T[i];
let b: Seconds = DELTA_T[i + 2] - DELTA_T[i + 1];
let c: Seconds = a - b;
let n = days_ratio(
jd - (JD_TABLE_START_1620 + BIENNIAL_STEP_D * i as f64),
BIENNIAL_STEP_D,
);
DELTA_T[i + 1] + n / 2.0 * (a + b + n * c)
}
#[inline]
fn delta_t_observed(jd: JulianDate) -> Seconds {
let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
let idx_f = year - OBSERVED_START_YEAR;
let idx = idx_f as usize;
if idx + 1 >= OBSERVED_TERMS {
return OBSERVED_DT[OBSERVED_TERMS - 1];
}
let frac = idx_f - idx as f64;
OBSERVED_DT[idx] + frac * (OBSERVED_DT[idx + 1] - OBSERVED_DT[idx])
}
#[inline]
fn delta_t_extrapolated(jd: JulianDate) -> Seconds {
let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
let dt_last = OBSERVED_DT[OBSERVED_TERMS - 1];
let years_past = year - OBSERVED_END_YEAR;
dt_last + Seconds::new(EXTRAPOLATION_RATE * years_past)
}
#[inline]
fn days_ratio(num: Days, den: Days) -> f64 {
(num / den).simplify().value()
}
const JD_1992: JulianDate = JulianDate::new(2_448_622.5);
const JD_2026: JulianDate = JulianDate::new(2_461_041.5);
#[inline]
pub(crate) fn delta_t_seconds_from_ut(jd_ut: JulianDate) -> Seconds {
match jd_ut {
jd if jd < JulianDate::new(2_067_314.5) => delta_t_ancient(jd),
jd if jd < JulianDate::new(2_305_447.5) => delta_t_medieval(jd),
jd if jd < JD_1992 => delta_t_table(jd),
jd if jd < JD_2026 => delta_t_observed(jd),
_ => delta_t_extrapolated(jd_ut),
}
}
impl Time<UT> {
#[inline]
pub fn delta_t(&self) -> Seconds {
delta_t_seconds_from_ut(JulianDate::from_days(self.quantity()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use qtty::{Day, Days};
#[test]
fn delta_t_ancient_sample() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_000_000.0));
assert!((dt - Seconds::new(2_734.342_214_024_879_5)).abs() < Seconds::new(1e-6));
}
#[test]
fn delta_t_medieval_sample() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_100_000.0));
assert!((dt - Seconds::new(1_485.280_240_204_242_3)).abs() < Seconds::new(1e-6));
}
#[test]
fn delta_t_table_sample() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_312_752.5));
assert!((dt - Seconds::new(115.0)).abs() < Seconds::new(1e-6));
}
#[test]
fn delta_t_table_upper_clip() {
let dt = delta_t_table(JulianDate::new(2_449_356.0));
assert!((dt - Seconds::new(59.3)).abs() < Seconds::new(1e-6));
}
#[test]
fn delta_t_2000() {
let dt = delta_t_seconds_from_ut(JulianDate::J2000);
assert!(
(dt - Seconds::new(63.83)).abs() < Seconds::new(0.1),
"ΔT at J2000 = {dt}, expected 63.83 s"
);
}
#[test]
fn delta_t_2010() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_455_197.5));
assert!(
(dt - Seconds::new(66.07)).abs() < Seconds::new(0.5),
"ΔT at 2010. = {dt}, expected ~66.07 s"
);
}
#[test]
fn delta_t_2020() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_458_849.5));
assert!(
(dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
"ΔT at 2020.0 = {dt}, expected ~69.36 s"
);
}
#[test]
fn delta_t_2025() {
let dt = delta_t_seconds_from_ut(JulianDate::new(2_460_676.5));
assert!(
(dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
"ΔT at 2025.0 = {dt}, expected ~69.36 s"
);
}
#[test]
fn delta_t_extrapolated_near_future() {
let jd_2030 = JulianDate::new(2_462_502.5);
let dt = delta_t_seconds_from_ut(jd_2030);
assert!(
(dt - Seconds::new(69.44)).abs() < Seconds::new(1.0),
"ΔT at 2030. = {dt}, expected ~69.44 s"
);
assert!(dt < Seconds::new(75.0), "ΔT at 2030 is too large: {dt}");
}
#[test]
fn ut_scale_applies_delta_t() {
let ut = Time::<UT>::new(2_451_545.0);
let jd_tt = ut.to::<crate::JD>();
let offset = jd_tt - JulianDate::new(2_451_545.0);
let expected = delta_t_seconds_from_ut(JulianDate::new(2_451_545.0)).to::<Day>();
assert!((offset - expected).abs() < Days::new(1e-9));
}
#[test]
fn ut_scale_roundtrip() {
let jd_tt = JulianDate::new(2_451_545.0);
let ut: Time<UT> = jd_tt.to::<UT>();
let back: JulianDate = ut.to::<crate::JD>();
assert!((back - jd_tt).abs() < Days::new(1e-12));
}
#[test]
fn delta_t_convenience_method() {
let ut = Time::<UT>::new(2_451_545.0);
let dt = ut.delta_t();
assert!((dt - Seconds::new(63.83)).abs() < Seconds::new(0.5));
}
}