pub mod eop;
pub mod model;
pub mod scales;
pub use eop::{
CoverageError, DegradeReason, LeapSecondTable, Ut1Provenance, Validated, ValidityMode,
};
pub use model::{
Duration, GnssWeekTow, Instant, InstantRepr, JulianDateSplit, TimeScale, SECONDS_PER_WEEK,
};
pub use scales::TimeScales;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Time {
pub seconds_since_j2000: f64,
}
impl Time {
pub fn new(seconds_since_j2000: f64) -> Self {
Self {
seconds_since_j2000,
}
}
pub fn tdb(&self) -> f64 {
self.seconds_since_j2000
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn week_tow_normalizes_overflow() {
let wt = GnssWeekTow::new(TimeScale::Gpst, 100, SECONDS_PER_WEEK + 5.0).normalized();
assert_eq!(wt.week, 101);
assert!((wt.tow_s - 5.0).abs() < 1e-9);
}
#[test]
fn week_tow_borrows_negative() {
let wt = GnssWeekTow::new(TimeScale::Gpst, 100, -10.0).normalized();
assert_eq!(wt.week, 99);
assert!((wt.tow_s - (SECONDS_PER_WEEK - 10.0)).abs() < 1e-6);
}
#[test]
fn week_rollover_unrolls() {
let wt = GnssWeekTow::new(TimeScale::Gpst, 10, 0.0);
assert_eq!(wt.unrolled_week(2), 10 + 2048);
}
#[test]
fn ut1_coverage_strict_vs_permissive() {
let prov = scales::ut1_coverage();
let mid = (prov.first_jd_tt + prov.last_jd_tt) / 2.0;
assert_eq!(
eop::check_ut1_coverage(&prov, mid, ValidityMode::Strict),
Ok(None)
);
let before = prov.first_jd_tt - 1.0;
assert!(eop::check_ut1_coverage(&prov, before, ValidityMode::Strict).is_err());
assert_eq!(
eop::check_ut1_coverage(&prov, before, ValidityMode::Permissive),
Ok(Some(DegradeReason::BeforeCoverage))
);
}
#[test]
fn time_scales_from_utc_unchanged_shape() {
let ts = TimeScales::from_utc(2000, 1, 1, 12, 0, 0.0);
assert!((ts.jd_tt - 2451545.0).abs() < 1e-3);
}
#[test]
fn from_utc_validated_in_coverage_is_bit_identical_and_not_degraded() {
let plain = TimeScales::from_utc(2000, 1, 1, 12, 0, 0.0);
for mode in [ValidityMode::Strict, ValidityMode::Permissive] {
let v = TimeScales::from_utc_validated(2000, 1, 1, 12, 0, 0.0, mode)
.expect("in-coverage instant must not error in either mode");
assert_eq!(v.degraded, None, "in-coverage must not be degraded");
assert_eq!(
v.value, plain,
"validated numerics must equal from_utc bit-for-bit"
);
}
}
#[test]
fn from_utc_validated_strict_errors_before_coverage() {
let prov = scales::ut1_coverage();
let (y, m, d) = (1960, 1, 1);
let plain = TimeScales::from_utc(y, m, d, 0, 0, 0.0);
assert!(
plain.jd_tt < prov.first_jd_tt,
"fixture must be before coverage"
);
let err = TimeScales::from_utc_validated(y, m, d, 0, 0, 0.0, ValidityMode::Strict)
.expect_err("strict mode must error before coverage");
assert_eq!(
err,
CoverageError::OutsideCoverage(DegradeReason::BeforeCoverage)
);
}
#[test]
fn from_utc_validated_strict_errors_after_coverage() {
let prov = scales::ut1_coverage();
let (y, m, d) = (2100, 1, 1);
let plain = TimeScales::from_utc(y, m, d, 0, 0, 0.0);
assert!(
plain.jd_tt > prov.last_jd_tt,
"fixture must be after coverage"
);
let err = TimeScales::from_utc_validated(y, m, d, 0, 0, 0.0, ValidityMode::Strict)
.expect_err("strict mode must error after coverage");
assert_eq!(
err,
CoverageError::OutsideCoverage(DegradeReason::AfterCoverage)
);
}
#[test]
fn from_utc_validated_permissive_clamps_and_marks_degraded() {
let plain_before = TimeScales::from_utc(1960, 1, 1, 0, 0, 0.0);
let before =
TimeScales::from_utc_validated(1960, 1, 1, 0, 0, 0.0, ValidityMode::Permissive)
.expect("permissive must not error");
assert_eq!(before.degraded, Some(DegradeReason::BeforeCoverage));
assert_eq!(before.value, plain_before);
let plain_after = TimeScales::from_utc(2100, 1, 1, 0, 0, 0.0);
let after = TimeScales::from_utc_validated(2100, 1, 1, 0, 0, 0.0, ValidityMode::Permissive)
.expect("permissive must not error");
assert_eq!(after.degraded, Some(DegradeReason::AfterCoverage));
assert_eq!(after.value, plain_after);
}
}