use crate::astro::eop::EopValues;
use crate::astro::sidereal::gmst_iau2006;
use crate::time::{JulianDate, UT};
use qtty::*;
#[inline]
pub fn jd_ut1_from_tt(jd_tt: JulianDate) -> JulianDate {
let ut1_value = jd_tt.to::<UT>().value();
JulianDate::new(ut1_value)
}
#[inline]
pub fn jd_ut1_from_tt_eop(jd_tt: JulianDate, eop: &EopValues) -> JulianDate {
if eop.dut1.value() == 0.0 {
return jd_ut1_from_tt(jd_tt);
}
const TT_MINUS_TAI: f64 = 32.184; let approx_utc = jd_tt.value() - (37.0 + TT_MINUS_TAI) / 86_400.0;
let dat = tempoch::tai_minus_utc(approx_utc);
let jd_utc = jd_tt.value() - (dat + TT_MINUS_TAI) / 86_400.0;
let jd_ut1 = jd_utc + eop.dut1.value() / 86_400.0;
JulianDate::new(jd_ut1)
}
#[inline]
pub fn gmst_from_tt(jd_tt: JulianDate) -> Radians {
let jd_ut1 = jd_ut1_from_tt(jd_tt);
gmst_iau2006(jd_ut1, jd_tt)
}
#[inline]
pub fn gmst_from_tt_eop(jd_tt: JulianDate, eop: &EopValues) -> Radians {
let jd_ut1 = jd_ut1_from_tt_eop(jd_tt, eop);
gmst_iau2006(jd_ut1, jd_tt)
}
#[inline]
pub fn gmst_default(jd_tt: JulianDate) -> Radians {
gmst_from_tt(jd_tt)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::astro::eop::EopValues;
use std::f64::consts::TAU;
const JD_J2000: f64 = 2451545.0;
fn jd() -> JulianDate {
JulianDate::new(JD_J2000)
}
#[test]
fn jd_ut1_is_close_to_tt_at_modern_epoch() {
let jd_ut1 = jd_ut1_from_tt(jd());
let diff_sec = (jd().value() - jd_ut1.value()) * 86400.0;
assert!(
diff_sec > 50.0 && diff_sec < 80.0,
"ΔT at J2000 expected ~63s, got {diff_sec}s"
);
}
#[test]
fn jd_ut1_eop_zero_dut1_falls_back_to_delta_t() {
let eop = EopValues::default(); let jd_eop = jd_ut1_from_tt_eop(jd(), &eop);
let jd_dt = jd_ut1_from_tt(jd());
assert!((jd_eop.value() - jd_dt.value()).abs() < 1e-12);
}
#[test]
fn jd_ut1_eop_nonzero_dut1_applies_correction() {
use qtty::Seconds;
let eop = EopValues {
dut1: Seconds::new(0.3),
..Default::default()
}; let jd_eop = jd_ut1_from_tt_eop(jd(), &eop);
assert!(jd_eop.value().is_finite());
let diff_days = (jd().value() - jd_eop.value()).abs();
assert!(
diff_days < 1e-3,
"UT1-TT difference too large: {diff_days} days"
);
}
#[test]
fn jd_ut1_eop_negative_dut1() {
use qtty::Seconds;
let eop = EopValues {
dut1: Seconds::new(-0.5),
..Default::default()
};
let jd_eop = jd_ut1_from_tt_eop(jd(), &eop);
assert!(jd_eop.value().is_finite());
}
#[test]
fn gmst_from_tt_is_in_range() {
let gmst = gmst_from_tt(jd());
assert!(
gmst.value() >= 0.0 && gmst.value() < TAU,
"GMST out of [0, 2π): {}",
gmst.value()
);
}
#[test]
fn gmst_from_tt_varies_with_time() {
let gmst1 = gmst_from_tt(jd());
let gmst2 = gmst_from_tt(JulianDate::new(JD_J2000 + 1.0));
let diff = (gmst2.value() - gmst1.value()).abs();
assert!(diff > 0.0, "GMST should change over 1 day");
}
#[test]
fn gmst_from_tt_eop_null_eop_matches_gmst_from_tt() {
let eop = EopValues::default(); let gmst_eop = gmst_from_tt_eop(jd(), &eop);
let gmst_dt = gmst_from_tt(jd());
assert!((gmst_eop.value() - gmst_dt.value()).abs() < 1e-12);
}
#[test]
fn gmst_from_tt_eop_with_nonzero_dut1() {
use qtty::Seconds;
let eop = EopValues {
dut1: Seconds::new(0.2),
..Default::default()
};
let gmst_eop = gmst_from_tt_eop(jd(), &eop);
assert!(gmst_eop.value().is_finite());
assert!(gmst_eop.value() >= 0.0);
}
#[test]
fn gmst_default_matches_gmst_from_tt() {
let jd_val = jd();
let g1 = gmst_default(jd_val);
let g2 = gmst_from_tt(jd_val);
assert!((g1.value() - g2.value()).abs() < 1e-15);
}
}