use core::fmt;
use supernovas_ffi::{
NOVAS_JD_B1900, NOVAS_JD_B1950, NOVAS_JD_HIP, NOVAS_JD_J2000, NOVAS_JD_MJD0,
NOVAS_JULIAN_YEAR_DAYS, novas_equator_type,
novas_equator_type::{NOVAS_GCRS_EQUATOR, NOVAS_MEAN_EQUATOR, NOVAS_TRUE_EQUATOR},
};
use crate::{
ReferenceSystem,
error::{Error, Result},
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Equinox {
name: &'static str,
system: ReferenceSystem,
jd_tt: f64,
}
impl Equinox {
pub const ICRS: Equinox = Equinox {
name: "ICRS",
system: ReferenceSystem::Icrs,
jd_tt: NOVAS_JD_J2000,
};
pub const J2000: Equinox = Equinox {
name: "J2000",
system: ReferenceSystem::J2000,
jd_tt: NOVAS_JD_J2000,
};
pub const HIPPARCOS: Equinox = Equinox {
name: "HIP",
system: ReferenceSystem::Mod,
jd_tt: NOVAS_JD_HIP,
};
pub const B1950: Equinox = Equinox {
name: "B1950",
system: ReferenceSystem::Mod,
jd_tt: NOVAS_JD_B1950,
};
pub const B1900: Equinox = Equinox {
name: "B1900",
system: ReferenceSystem::Mod,
jd_tt: NOVAS_JD_B1900,
};
pub fn mod_at(jd_tt: f64) -> Result<Self> {
Self::at("MOD", ReferenceSystem::Mod, jd_tt)
}
pub fn tod_at(jd_tt: f64) -> Result<Self> {
Self::at("TOD", ReferenceSystem::Tod, jd_tt)
}
pub fn cirs_at(jd_tt: f64) -> Result<Self> {
Self::at("CIRS", ReferenceSystem::Cirs, jd_tt)
}
pub fn at(name: &'static str, system: ReferenceSystem, jd_tt: f64) -> Result<Self> {
if !jd_tt.is_finite() {
return Err(Error::NotFinite);
}
Ok(Equinox {
name,
system,
jd_tt,
})
}
pub fn name(self) -> &'static str {
self.name
}
pub fn system(self) -> ReferenceSystem {
self.system
}
pub fn jd(self) -> f64 {
self.jd_tt
}
pub fn mjd(self) -> f64 {
self.jd_tt - NOVAS_JD_MJD0
}
pub fn epoch(self) -> f64 {
2000.0 + (self.jd_tt - NOVAS_JD_J2000) / NOVAS_JULIAN_YEAR_DAYS
}
pub fn is_icrs(self) -> bool {
matches!(self.system, ReferenceSystem::Icrs | ReferenceSystem::Gcrs)
}
pub fn is_mod(self) -> bool {
matches!(self.system, ReferenceSystem::Mod | ReferenceSystem::J2000)
}
pub fn is_true(self) -> bool {
matches!(self.system, ReferenceSystem::Tod | ReferenceSystem::Tirs)
}
pub(crate) fn equator_type_for_ecliptic(self) -> Option<novas_equator_type> {
match self.system {
ReferenceSystem::Icrs | ReferenceSystem::Gcrs => Some(NOVAS_GCRS_EQUATOR),
ReferenceSystem::J2000 | ReferenceSystem::Mod => Some(NOVAS_MEAN_EQUATOR),
ReferenceSystem::Tod | ReferenceSystem::Tirs => Some(NOVAS_TRUE_EQUATOR),
ReferenceSystem::Cirs | ReferenceSystem::Itrs => None,
}
}
}
impl approx::AbsDiffEq for Equinox {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
1.0 / 86_400.0
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.system == other.system && (self.jd_tt - other.jd_tt).abs() <= epsilon
}
}
impl fmt::Display for Equinox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name)
}
}
#[cfg(test)]
mod tests {
use approx::{AbsDiffEq, assert_abs_diff_eq};
use super::*;
#[test]
fn predefined_constants_have_right_systems() {
assert_eq!(Equinox::ICRS.system(), ReferenceSystem::Icrs);
assert_eq!(Equinox::J2000.system(), ReferenceSystem::J2000);
assert_eq!(Equinox::HIPPARCOS.system(), ReferenceSystem::Mod);
assert_eq!(Equinox::B1950.system(), ReferenceSystem::Mod);
}
#[test]
fn j2000_epoch_is_2000() {
assert!((Equinox::J2000.epoch() - 2000.0).abs() < 1e-9);
}
#[test]
fn b1950_epoch_is_about_1950() {
assert!((Equinox::B1950.epoch() - 1950.0).abs() < 1.0);
}
#[test]
fn mjd_offset_is_jd_minus_2400000_5() {
assert!((Equinox::J2000.mjd() - (NOVAS_JD_J2000 - NOVAS_JD_MJD0)).abs() < 1e-9);
}
#[test]
fn rejects_non_finite_jd() {
assert!(matches!(Equinox::mod_at(f64::NAN), Err(Error::NotFinite)));
}
#[test]
fn date_factories_carry_the_jd_through() {
let e = Equinox::mod_at(2_460_676.5).unwrap();
assert_eq!(e.system(), ReferenceSystem::Mod);
assert_eq!(e.jd(), 2_460_676.5);
}
#[test]
fn predicates() {
assert!(Equinox::ICRS.is_icrs());
assert!(Equinox::J2000.is_mod()); assert!(!Equinox::J2000.is_true());
let tod = Equinox::tod_at(NOVAS_JD_J2000).unwrap();
assert!(tod.is_true());
assert!(!tod.is_mod());
}
#[test]
fn abs_diff_eq_matches_system_and_date() {
let a = Equinox::mod_at(NOVAS_JD_J2000).unwrap();
let b = Equinox::mod_at(NOVAS_JD_J2000 + 1e-7).unwrap();
assert_abs_diff_eq!(a, b);
let c = Equinox::tod_at(NOVAS_JD_J2000).unwrap();
assert!(!a.abs_diff_eq(&c, 1.0));
}
}