use super::{ToGPS, ToTAI};
use crate::constants::GPS_TO_TAI_OFFSET_SECONDS;
use crate::scales::{GPS, TAI};
use crate::TimeResult;
use celestial_core::constants::SECONDS_PER_DAY_F64;
impl ToGPS for GPS {
fn to_gps(&self) -> TimeResult<GPS> {
Ok(*self)
}
}
impl ToTAI for GPS {
fn to_tai(&self) -> TimeResult<TAI> {
let gps_jd = self.to_julian_date();
let offset_days = GPS_TO_TAI_OFFSET_SECONDS / SECONDS_PER_DAY_F64;
let (tai_jd1, tai_jd2) = if gps_jd.jd1().abs() > gps_jd.jd2().abs() {
(gps_jd.jd1(), gps_jd.jd2() + offset_days)
} else {
(gps_jd.jd1() + offset_days, gps_jd.jd2())
};
Ok(TAI::from_julian_date_raw(tai_jd1, tai_jd2))
}
}
impl ToGPS for TAI {
fn to_gps(&self) -> TimeResult<GPS> {
let tai_jd = self.to_julian_date();
let offset_days = GPS_TO_TAI_OFFSET_SECONDS / SECONDS_PER_DAY_F64;
let (gps_jd1, gps_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(GPS::from_julian_date_raw(gps_jd1, gps_jd2))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::GPS_EPOCH_JD;
use crate::JulianDate;
use celestial_core::constants::J2000_JD;
#[test]
fn test_gps_identity_conversion() {
let gps = GPS::from_julian_date(JulianDate::new(J2000_JD, 0.999999999999999));
let identity_gps = gps.to_gps().unwrap();
assert_eq!(
gps.to_julian_date().jd1(),
identity_gps.to_julian_date().jd1(),
"GPS identity conversion should preserve JD1"
);
assert_eq!(
gps.to_julian_date().jd2(),
identity_gps.to_julian_date().jd2(),
"GPS identity conversion should preserve JD2"
);
}
#[test]
fn test_gps_tai_offset_19_seconds() {
let test_dates = [
(GPS_EPOCH_JD, "GPS epoch 1980-01-06"),
(J2000_JD, "J2000.0"),
(2455197.5, "2010-01-01"),
(2459580.5, "2022-01-01"),
];
for (jd, description) in test_dates {
let gps = GPS::from_julian_date(JulianDate::new(jd, 0.0));
let tai = gps.to_tai().unwrap();
let gps_jd = gps.to_julian_date();
let tai_jd = tai.to_julian_date();
let offset_days = (tai_jd.jd1() - gps_jd.jd1()) + (tai_jd.jd2() - gps_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, 19.0,
"{}: GPS->TAI offset must be exactly 19 seconds",
description
);
let tai = TAI::from_julian_date(JulianDate::new(jd, 0.0));
let gps = tai.to_gps().unwrap();
let tai_jd = tai.to_julian_date();
let gps_jd = gps.to_julian_date();
let offset_days = (tai_jd.jd1() - gps_jd.jd1()) + (tai_jd.jd2() - gps_jd.jd2());
let offset_seconds = offset_days * SECONDS_PER_DAY_F64;
assert_eq!(
offset_seconds, 19.0,
"{}: TAI->GPS means TAI is 19 seconds ahead",
description
);
}
}
#[test]
fn test_gps_tai_round_trip_precision() {
let test_jd2_values = [0.0, 0.5, 0.123456789012345, -0.123456789012345, 0.987654321];
for jd2 in test_jd2_values {
let original_gps = GPS::from_julian_date(JulianDate::new(J2000_JD, jd2));
let tai = original_gps.to_tai().unwrap();
let round_trip_gps = tai.to_gps().unwrap();
assert_eq!(
original_gps.to_julian_date().jd1(),
round_trip_gps.to_julian_date().jd1(),
"GPS->TAI->GPS JD1 must be exact for jd2={}",
jd2
);
assert_eq!(
original_gps.to_julian_date().jd2(),
round_trip_gps.to_julian_date().jd2(),
"GPS->TAI->GPS JD2 must be exact for jd2={}",
jd2
);
let original_tai = TAI::from_julian_date(JulianDate::new(J2000_JD, jd2));
let gps = original_tai.to_gps().unwrap();
let round_trip_tai = gps.to_tai().unwrap();
assert_eq!(
original_tai.to_julian_date().jd1(),
round_trip_tai.to_julian_date().jd1(),
"TAI->GPS->TAI JD1 must be exact for jd2={}",
jd2
);
assert_eq!(
original_tai.to_julian_date().jd2(),
round_trip_tai.to_julian_date().jd2(),
"TAI->GPS->TAI JD2 must be exact for jd2={}",
jd2
);
}
let alt_gps = GPS::from_julian_date(JulianDate::new(0.5, J2000_JD));
let alt_tai = alt_gps.to_tai().unwrap();
let alt_round_trip = alt_tai.to_gps().unwrap();
assert_eq!(
alt_gps.to_julian_date().jd1(),
alt_round_trip.to_julian_date().jd1(),
"Alternate split GPS->TAI->GPS JD1 must be exact"
);
assert_eq!(
alt_gps.to_julian_date().jd2(),
alt_round_trip.to_julian_date().jd2(),
"Alternate split GPS->TAI->GPS JD2 must be exact"
);
}
}