use crate::epoch::{J2000_NOON_TJT, J2000_TAI_TJT, SECONDS_PER_DAY, TAI_TT_OFFSET};
use crate::leap_second::LeapSecondTable;
use crate::time_converter_tai_tdb;
use crate::time_converter_tai_tt;
use crate::time_converter_ut1_gmst;
use crate::time_dyn::DynamicTime;
use crate::time_gps;
use crate::time_met::MissionElapsedTime;
use crate::time_ude::UserDefinedEpoch;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeScaleId {
TAI,
TT,
TDB,
UTC,
UT1,
GMST,
GPS,
MET,
DYN,
}
#[derive(Debug, Clone)]
pub struct TimeManager {
pub tai_seconds: f64,
pub tai_tjt: f64,
pub tai_tjt_at_epoch: f64,
pub tt_seconds: f64,
pub tdb_seconds: f64,
pub utc_seconds: f64,
pub ut1_seconds: f64,
pub gmst_radians: f64,
pub gmst_seconds: f64,
pub gps_seconds: f64,
pub leap_second_table: LeapSecondTable,
pub ut1_tai_offset: f64,
utc_tjt_at_epoch: f64,
dyn_time: DynamicTime,
pub simtime: f64,
pub met: Option<MissionElapsedTime>,
pub ude: Vec<UserDefinedEpoch>,
}
impl TimeManager {
pub fn new(tai_tjt_at_epoch: f64, leap_table: LeapSecondTable) -> Self {
let tai_utc_s = leap_table.tai_utc_at_tai_tjt(tai_tjt_at_epoch);
let ut1_tai_offset = -tai_utc_s;
let utc_tjt_at_epoch = leap_table.tai_to_utc_tjt(tai_tjt_at_epoch);
let mut mgr = Self {
tai_seconds: 0.0,
tai_tjt: tai_tjt_at_epoch,
tai_tjt_at_epoch,
tt_seconds: 0.0,
tdb_seconds: 0.0,
utc_seconds: 0.0,
ut1_seconds: 0.0,
gmst_radians: 0.0,
gmst_seconds: 0.0,
gps_seconds: 0.0,
leap_second_table: leap_table,
ut1_tai_offset,
utc_tjt_at_epoch,
dyn_time: DynamicTime::new(),
simtime: 0.0,
met: None,
ude: Vec::new(),
};
mgr.update_all();
mgr
}
pub fn at_j2000(leap_table: LeapSecondTable) -> Self {
Self::new(J2000_TAI_TJT, leap_table)
}
pub fn set_ut1_tai_offset(&mut self, offset_seconds: f64) {
self.ut1_tai_offset = offset_seconds;
self.update_all();
}
pub fn set_scale_factor(&mut self, factor: f64) {
self.dyn_time.scale_factor = factor;
}
pub fn scale_factor(&self) -> f64 {
self.dyn_time.scale_factor
}
pub fn add_met(&mut self, tai_seconds_at_epoch: f64) {
let mut met = MissionElapsedTime::new(tai_seconds_at_epoch);
met.update(self.tai_seconds);
self.met = Some(met);
}
pub fn add_ude(&mut self, epoch_in_parent: f64) -> usize {
let idx = self.ude.len();
let mut ude = UserDefinedEpoch::new(epoch_in_parent);
ude.update(self.tai_seconds);
self.ude.push(ude);
idx
}
pub fn advance(&mut self, sim_dt: f64) {
assert!(
sim_dt.is_finite() && sim_dt >= 0.0,
"sim_dt must be finite and >= 0, got {sim_dt}"
);
self.dyn_time.update_offset(self.simtime);
self.simtime += sim_dt;
self.dyn_time.update(self.simtime);
self.tai_seconds = self.dyn_time.seconds;
self.tai_tjt = self.tai_tjt_at_epoch + self.tai_seconds / SECONDS_PER_DAY;
self.update_all();
}
pub fn get_seconds(&self, scale: TimeScaleId) -> f64 {
match scale {
TimeScaleId::TAI => self.tai_seconds,
TimeScaleId::TT => self.tt_seconds,
TimeScaleId::TDB => self.tdb_seconds,
TimeScaleId::UTC => self.utc_seconds,
TimeScaleId::UT1 => self.ut1_seconds,
TimeScaleId::GMST => self.gmst_seconds,
TimeScaleId::GPS => self.gps_seconds,
TimeScaleId::DYN => self.dyn_time.seconds,
TimeScaleId::MET => {
self.met
.as_ref()
.expect("MET scale not registered; call add_met() first")
.seconds
}
}
}
pub fn get_met_seconds(&self) -> Option<f64> {
self.met.as_ref().map(|m| m.seconds)
}
pub fn get_ude_seconds(&self, idx: usize) -> Option<f64> {
self.ude.get(idx).map(|u| u.seconds)
}
pub fn get_ude(&self, idx: usize) -> &UserDefinedEpoch {
&self.ude[idx]
}
pub fn tdb_julian_date(&self) -> f64 {
let tdb_tai_offset_s = self.tdb_seconds - self.tai_seconds;
let tdb_tjt = self.tai_tjt + tdb_tai_offset_s / SECONDS_PER_DAY;
tdb_tjt + 40_000.0 + 2_400_000.5
}
pub fn tt_tjt(&self) -> f64 {
self.tai_tjt + TAI_TT_OFFSET / SECONDS_PER_DAY
}
pub fn tt_julian_date(&self) -> f64 {
self.tt_tjt() + 40_000.0 + 2_400_000.5
}
fn update_all(&mut self) {
self.tt_seconds = time_converter_tai_tt::tai_to_tt(self.tai_seconds);
self.tdb_seconds = time_converter_tai_tdb::tai_to_tdb(self.tai_seconds, self.tai_tjt);
let utc_tjt = self.leap_second_table.tai_to_utc_tjt(self.tai_tjt);
self.utc_seconds = (utc_tjt - self.utc_tjt_at_epoch) * SECONDS_PER_DAY;
self.gps_seconds = time_gps::tai_to_gps(self.tai_seconds);
self.ut1_seconds = self.tai_seconds + self.ut1_tai_offset;
let ut1_tjt = self.tai_tjt + self.ut1_tai_offset / SECONDS_PER_DAY;
let du = ut1_tjt - J2000_NOON_TJT;
self.gmst_seconds = time_converter_ut1_gmst::ut1_to_gmst_seconds(du);
self.gmst_radians = time_converter_ut1_gmst::ut1_to_gmst_radians(du);
if let Some(ref mut met) = self.met {
met.update(self.tai_seconds);
}
for ude in &mut self.ude {
ude.update(self.tai_seconds);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::leap_second::default_leap_second_table;
use std::f64::consts::PI;
#[test]
fn time_manager_at_j2000() {
let mgr = TimeManager::at_j2000(default_leap_second_table());
assert_eq!(mgr.tai_seconds, 0.0);
assert_eq!(mgr.simtime, 0.0);
assert!((mgr.tt_seconds - 32.184).abs() < 1e-10);
assert!((mgr.gps_seconds - (-19.0)).abs() < 1e-15);
}
#[test]
fn time_manager_advance_updates_all() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
mgr.advance(3600.0);
assert!((mgr.tai_seconds - 3600.0).abs() < 1e-15);
assert!((mgr.tt_seconds - (3600.0 + 32.184)).abs() < 1e-10);
assert!((mgr.gps_seconds - (3600.0 - 19.0)).abs() < 1e-15);
assert!((mgr.simtime - 3600.0).abs() < 1e-15);
assert!((mgr.get_seconds(TimeScaleId::DYN) - 3600.0).abs() < 1e-15);
}
#[test]
fn time_manager_get_seconds() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
mgr.advance(100.0);
assert!((mgr.get_seconds(TimeScaleId::TAI) - 100.0).abs() < 1e-15);
assert!((mgr.get_seconds(TimeScaleId::TT) - 132.184).abs() < 1e-10);
assert!((mgr.get_seconds(TimeScaleId::GPS) - 81.0).abs() < 1e-15);
assert!((mgr.get_seconds(TimeScaleId::DYN) - 100.0).abs() < 1e-15);
}
#[test]
fn time_manager_with_met() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
mgr.add_met(0.0); mgr.advance(500.0);
assert!(
(mgr.met.as_ref().unwrap().seconds - 500.0).abs() < 1e-15,
"MET should be 500s"
);
}
#[test]
fn time_manager_with_ude() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
let idx = mgr.add_ude(1000.0); mgr.advance(1500.0);
assert!(
(mgr.ude[idx].seconds - 500.0).abs() < 1e-15,
"UDE should be 500s"
);
}
#[test]
fn time_manager_gmst_at_j2000() {
let mgr = TimeManager::at_j2000(default_leap_second_table());
let gmst_deg = mgr.gmst_radians * 180.0 / PI;
assert!(
(gmst_deg - 280.19).abs() < 0.05,
"GMST at J2000: {:.4} degrees, expected ~280.19",
gmst_deg
);
}
#[test]
fn time_manager_tdb_julian_date() {
let mgr = TimeManager::at_j2000(default_leap_second_table());
let jd = mgr.tdb_julian_date();
assert!((jd - 2_451_545.0).abs() < 0.001, "TDB JD at J2000: {}", jd);
}
#[test]
fn time_manager_scale_factor() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
mgr.advance(100.0);
let tai_100 = mgr.tai_seconds;
mgr.set_scale_factor(-1.0);
mgr.advance(100.0);
assert!(
mgr.tai_seconds.abs() < 1e-15,
"TAI should return to 0 after reversal, got {}",
mgr.tai_seconds
);
let _ = tai_100;
}
#[test]
fn time_manager_multiple_udes() {
let mut mgr = TimeManager::at_j2000(default_leap_second_table());
let idx0 = mgr.add_ude(0.0);
let idx1 = mgr.add_ude(500.0);
mgr.advance(1000.0);
assert!((mgr.ude[idx0].seconds - 1000.0).abs() < 1e-15);
assert!((mgr.ude[idx1].seconds - 500.0).abs() < 1e-15);
}
}