use crate::data::active::{active_time_data, time_data_eop_at};
use qtty::{Day, Second};
#[cfg(test)]
use crate::generated::eop_data::{EOP_END_MJD, EOP_POINTS, EOP_START_MJD};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EopValues {
pub mjd_utc: Day,
pub pm_xp_arcsec: Option<f64>,
pub pm_yp_arcsec: Option<f64>,
pub ut1_minus_utc: Second,
pub lod_milliseconds: Option<f64>,
pub dx_milliarcsec: Option<f64>,
pub dy_milliarcsec: Option<f64>,
pub ut1_observed: bool,
}
#[inline]
pub fn builtin_eop_covers(mjd_utc: Day) -> bool {
let data = active_time_data();
time_data_eop_at(data.as_ref(), mjd_utc).is_some()
}
pub fn builtin_eop_at(mjd_utc: Day) -> Option<EopValues> {
let data = active_time_data();
time_data_eop_at(data.as_ref(), mjd_utc)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn covers_start_and_end() {
assert!(builtin_eop_covers(Day::new(EOP_START_MJD as f64)));
assert!(builtin_eop_covers(Day::new(EOP_END_MJD as f64)));
assert!(!builtin_eop_covers(Day::new(EOP_START_MJD as f64 - 1.0)));
assert!(!builtin_eop_covers(Day::new(EOP_END_MJD as f64 + 1.0)));
}
#[test]
fn exact_point_matches_source() {
let mid = EOP_POINTS[EOP_POINTS.len() / 2];
let got = builtin_eop_at(Day::new(mid.mjd as f64)).unwrap();
assert_eq!(got.pm_xp_arcsec, mid.pm_xp_arcsec);
assert_eq!(got.pm_yp_arcsec, mid.pm_yp_arcsec);
assert!(
(got.ut1_minus_utc.value() - mid.ut1_minus_utc_seconds).abs() < 1e-12,
"ut1: {} vs {}",
got.ut1_minus_utc.value(),
mid.ut1_minus_utc_seconds
);
assert_eq!(got.dx_milliarcsec, mid.dx_milliarcsec);
assert_eq!(got.dy_milliarcsec, mid.dy_milliarcsec);
}
#[test]
fn midpoint_is_halfway() {
let lo = EOP_POINTS[100];
let hi = EOP_POINTS[101];
let got = builtin_eop_at(Day::new(lo.mjd as f64 + 0.5)).unwrap();
let expected = 0.5 * (lo.ut1_minus_utc_seconds + hi.ut1_minus_utc_seconds);
assert!((got.ut1_minus_utc.value() - expected).abs() < 1e-12);
}
#[test]
fn missing_optional_fields_remain_missing() {
let idx = EOP_POINTS
.windows(2)
.position(|window| {
window[0].dx_milliarcsec.is_none() && window[1].dx_milliarcsec.is_none()
})
.expect("generated EOP tail should include rows with blank nutation fields");
let lo = EOP_POINTS[idx];
let got = builtin_eop_at(Day::new(lo.mjd as f64 + 0.5)).unwrap();
assert_eq!(got.dx_milliarcsec, None);
assert_eq!(got.dy_milliarcsec, None);
}
#[test]
fn out_of_range_returns_none() {
assert!(builtin_eop_at(Day::new(EOP_START_MJD as f64 - 10.0)).is_none());
assert!(builtin_eop_at(Day::new(EOP_END_MJD as f64 + 10.0)).is_none());
}
}