use super::ToUT1;
use crate::julian::JulianDate;
use crate::scales::{TAI, TT, UT1};
use crate::TimeResult;
use celestial_core::constants::SECONDS_PER_DAY_F64;
impl ToUT1 for UT1 {
fn to_ut1(&self) -> TimeResult<UT1> {
Ok(*self)
}
}
pub trait ToUT1WithOffset {
fn to_ut1_with_offset(&self, ut1_tai_offset_seconds: f64) -> TimeResult<UT1>;
}
pub trait ToTAIWithOffset {
fn to_tai_with_offset(&self, ut1_tai_offset_seconds: f64) -> TimeResult<TAI>;
}
impl ToTAIWithOffset for UT1 {
fn to_tai_with_offset(&self, ut1_tai_offset_seconds: f64) -> TimeResult<TAI> {
let ut1_jd = self.to_julian_date();
let offset_days = ut1_tai_offset_seconds / SECONDS_PER_DAY_F64;
let (tai_jd1, tai_jd2) = if ut1_jd.jd1().abs() > ut1_jd.jd2().abs() {
(ut1_jd.jd1(), ut1_jd.jd2() - offset_days)
} else {
(ut1_jd.jd1() - offset_days, ut1_jd.jd2())
};
Ok(TAI::from_julian_date(JulianDate::new(tai_jd1, tai_jd2)))
}
}
impl ToUT1WithOffset for TAI {
fn to_ut1_with_offset(&self, ut1_tai_offset_seconds: f64) -> TimeResult<UT1> {
let tai_jd = self.to_julian_date();
let offset_days = ut1_tai_offset_seconds / SECONDS_PER_DAY_F64;
let (ut1_jd1, ut1_jd2) = if tai_jd.jd1().abs() > tai_jd.jd2().abs() {
(tai_jd.jd1(), tai_jd.jd2() + offset_days)
} else {
(tai_jd.jd1() + offset_days, tai_jd.jd2())
};
Ok(UT1::from_julian_date(JulianDate::new(ut1_jd1, ut1_jd2)))
}
}
pub trait ToTTWithDeltaT {
fn to_tt_with_delta_t(&self, delta_t_seconds: f64) -> TimeResult<TT>;
}
pub trait ToUT1WithDeltaT {
fn to_ut1_with_delta_t(&self, delta_t_seconds: f64) -> TimeResult<UT1>;
}
impl ToTTWithDeltaT for UT1 {
fn to_tt_with_delta_t(&self, delta_t_seconds: f64) -> TimeResult<TT> {
let ut1_jd = self.to_julian_date();
let delta_t_days = delta_t_seconds / SECONDS_PER_DAY_F64;
let (tt_jd1, tt_jd2) = if ut1_jd.jd1().abs() > ut1_jd.jd2().abs() {
(ut1_jd.jd1(), ut1_jd.jd2() + delta_t_days)
} else {
(ut1_jd.jd1() + delta_t_days, ut1_jd.jd2())
};
Ok(TT::from_julian_date(JulianDate::new(tt_jd1, tt_jd2)))
}
}
impl ToUT1WithDeltaT for TT {
fn to_ut1_with_delta_t(&self, delta_t_seconds: f64) -> TimeResult<UT1> {
let tt_jd = self.to_julian_date();
let delta_t_days = delta_t_seconds / SECONDS_PER_DAY_F64;
let (ut1_jd1, ut1_jd2) = if tt_jd.jd1().abs() > tt_jd.jd2().abs() {
(tt_jd.jd1(), tt_jd.jd2() - delta_t_days)
} else {
(tt_jd.jd1() - delta_t_days, tt_jd.jd2())
};
Ok(UT1::from_julian_date(JulianDate::new(ut1_jd1, ut1_jd2)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use celestial_core::constants::J2000_JD;
#[test]
fn test_ut1_identity_conversion() {
let ut1 = UT1::from_julian_date(JulianDate::new(J2000_JD, 0.999999999999999));
let identity_ut1 = ut1.to_ut1().unwrap();
assert_eq!(
ut1.to_julian_date().jd1(),
identity_ut1.to_julian_date().jd1(),
"UT1 identity conversion should preserve JD1 exactly"
);
assert_eq!(
ut1.to_julian_date().jd2(),
identity_ut1.to_julian_date().jd2(),
"UT1 identity conversion should preserve JD2 exactly"
);
}
#[test]
fn test_ut1_tai_offset_applied_correctly() {
let test_dates = [
(J2000_JD, "J2000.0"),
(2455197.5, "2010-01-01"),
(2459580.5, "2022-01-01"),
];
let ut1_tai_offset = -32.3;
let delta_t = 69.0;
for (jd, description) in test_dates {
let ut1 = UT1::from_julian_date(JulianDate::new(jd, 0.0));
let tai = ut1.to_tai_with_offset(ut1_tai_offset).unwrap();
let ut1_jd = ut1.to_julian_date();
let tai_jd = tai.to_julian_date();
let offset_days = (tai_jd.jd1() - ut1_jd.jd1()) + (tai_jd.jd2() - ut1_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, -ut1_tai_offset,
"{}: UT1->TAI offset must be exactly {} seconds",
description, -ut1_tai_offset
);
let tai = TAI::from_julian_date(JulianDate::new(jd, 0.0));
let ut1 = tai.to_ut1_with_offset(ut1_tai_offset).unwrap();
let tai_jd = tai.to_julian_date();
let ut1_jd = ut1.to_julian_date();
let offset_days = (tai_jd.jd1() - ut1_jd.jd1()) + (tai_jd.jd2() - ut1_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, -ut1_tai_offset,
"{}: TAI->UT1 means TAI is {} seconds ahead",
description, -ut1_tai_offset
);
let ut1 = UT1::from_julian_date(JulianDate::new(jd, 0.0));
let tt = ut1.to_tt_with_delta_t(delta_t).unwrap();
let ut1_jd = ut1.to_julian_date();
let tt_jd = tt.to_julian_date();
let offset_days = (tt_jd.jd1() - ut1_jd.jd1()) + (tt_jd.jd2() - ut1_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, delta_t,
"{}: UT1->TT offset must be exactly {} seconds",
description, delta_t
);
let tt = TT::from_julian_date(JulianDate::new(jd, 0.0));
let ut1 = tt.to_ut1_with_delta_t(delta_t).unwrap();
let tt_jd = tt.to_julian_date();
let ut1_jd = ut1.to_julian_date();
let offset_days = (tt_jd.jd1() - ut1_jd.jd1()) + (tt_jd.jd2() - ut1_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, delta_t,
"{}: TT->UT1 means TT is {} seconds ahead",
description, delta_t
);
}
}
#[test]
fn test_ut1_tai_round_trip_precision() {
const TOLERANCE_DAYS: f64 = 1e-14;
let test_jd2_values = [0.0, 0.5, 0.123456789012345, -0.123456789012345, 0.987654321];
let test_offsets = [-32.0, -31.8, -32.5, -33.1, -30.9];
for jd2 in test_jd2_values {
for &offset in &test_offsets {
let original_ut1 = UT1::from_julian_date(JulianDate::new(J2000_JD, jd2));
let tai = original_ut1.to_tai_with_offset(offset).unwrap();
let round_trip_ut1 = tai.to_ut1_with_offset(offset).unwrap();
assert_eq!(
original_ut1.to_julian_date().jd1(),
round_trip_ut1.to_julian_date().jd1(),
"UT1->TAI->UT1 JD1 must be exact for jd2={}, offset={}",
jd2,
offset
);
let jd2_diff = (original_ut1.to_julian_date().jd2()
- round_trip_ut1.to_julian_date().jd2())
.abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"UT1->TAI->UT1 JD2 diff {} exceeds tolerance {} for jd2={}, offset={}",
jd2_diff,
TOLERANCE_DAYS,
jd2,
offset
);
let original_tai = TAI::from_julian_date(JulianDate::new(J2000_JD, jd2));
let ut1 = original_tai.to_ut1_with_offset(offset).unwrap();
let round_trip_tai = ut1.to_tai_with_offset(offset).unwrap();
assert_eq!(
original_tai.to_julian_date().jd1(),
round_trip_tai.to_julian_date().jd1(),
"TAI->UT1->TAI JD1 must be exact for jd2={}, offset={}",
jd2,
offset
);
let jd2_diff = (original_tai.to_julian_date().jd2()
- round_trip_tai.to_julian_date().jd2())
.abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"TAI->UT1->TAI JD2 diff {} exceeds tolerance {} for jd2={}, offset={}",
jd2_diff,
TOLERANCE_DAYS,
jd2,
offset
);
}
}
let alt_ut1 = UT1::from_julian_date(JulianDate::new(0.5, J2000_JD));
let alt_tai = alt_ut1.to_tai_with_offset(-32.0).unwrap();
let alt_round_trip = alt_tai.to_ut1_with_offset(-32.0).unwrap();
assert_eq!(
alt_ut1.to_julian_date().jd1(),
alt_round_trip.to_julian_date().jd1(),
"Alternate split UT1->TAI->UT1 JD1 must be exact"
);
let jd2_diff =
(alt_ut1.to_julian_date().jd2() - alt_round_trip.to_julian_date().jd2()).abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"Alternate split UT1->TAI->UT1 JD2 diff {} exceeds tolerance {}",
jd2_diff,
TOLERANCE_DAYS
);
}
#[test]
fn test_ut1_tt_round_trip_precision() {
const TOLERANCE_DAYS: f64 = 1e-14;
let test_jd2_values = [0.0, 0.5, 0.123456789012345, -0.123456789012345, 0.987654321];
let test_delta_t_values = [63.8, 69.0, 70.5, 65.2];
for jd2 in test_jd2_values {
for &delta_t in &test_delta_t_values {
let original_ut1 = UT1::from_julian_date(JulianDate::new(J2000_JD, jd2));
let tt = original_ut1.to_tt_with_delta_t(delta_t).unwrap();
let round_trip_ut1 = tt.to_ut1_with_delta_t(delta_t).unwrap();
assert_eq!(
original_ut1.to_julian_date().jd1(),
round_trip_ut1.to_julian_date().jd1(),
"UT1->TT->UT1 JD1 must be exact for jd2={}, delta_t={}",
jd2,
delta_t
);
let jd2_diff = (original_ut1.to_julian_date().jd2()
- round_trip_ut1.to_julian_date().jd2())
.abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"UT1->TT->UT1 JD2 diff {} exceeds tolerance {} for jd2={}, delta_t={}",
jd2_diff,
TOLERANCE_DAYS,
jd2,
delta_t
);
let original_tt = TT::from_julian_date(JulianDate::new(J2000_JD, jd2));
let ut1 = original_tt.to_ut1_with_delta_t(delta_t).unwrap();
let round_trip_tt = ut1.to_tt_with_delta_t(delta_t).unwrap();
assert_eq!(
original_tt.to_julian_date().jd1(),
round_trip_tt.to_julian_date().jd1(),
"TT->UT1->TT JD1 must be exact for jd2={}, delta_t={}",
jd2,
delta_t
);
let jd2_diff = (original_tt.to_julian_date().jd2()
- round_trip_tt.to_julian_date().jd2())
.abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"TT->UT1->TT JD2 diff {} exceeds tolerance {} for jd2={}, delta_t={}",
jd2_diff,
TOLERANCE_DAYS,
jd2,
delta_t
);
}
}
let alt_ut1 = UT1::from_julian_date(JulianDate::new(0.5, J2000_JD));
let alt_tt = alt_ut1.to_tt_with_delta_t(69.0).unwrap();
let alt_round_trip = alt_tt.to_ut1_with_delta_t(69.0).unwrap();
assert_eq!(
alt_ut1.to_julian_date().jd1(),
alt_round_trip.to_julian_date().jd1(),
"Alternate split UT1->TT->UT1 JD1 must be exact"
);
let jd2_diff =
(alt_ut1.to_julian_date().jd2() - alt_round_trip.to_julian_date().jd2()).abs();
assert!(
jd2_diff <= TOLERANCE_DAYS,
"Alternate split UT1->TT->UT1 JD2 diff {} exceeds tolerance {}",
jd2_diff,
TOLERANCE_DAYS
);
}
}