#![expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
reason = "We need to review the math for overflows"
)]
use std::time::Duration;
use crate::time::{GpsTime, UtcParams, UtcTime, consts};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct MJD(f64);
impl MJD {
#[must_use]
pub fn from_f64(value: f64) -> Self {
assert!(value.is_finite());
Self(value)
}
#[must_use]
pub fn from_parts(year: u16, month: u8, day: u8, hour: u8, minute: u8, seconds: f64) -> MJD {
assert!(
year > 1858
|| (year == 1858 && month > 11)
|| (year == 1858 && month == 11 && day >= 17),
"Attempting to convert a date prior to the start of the Modified Julian Date system \
({year}-{month}-{day}T{hour}:{minute}:{seconds}Z"
);
let full_days = 367 * i64::from(year)
- 7 * (i64::from(year) + (i64::from(month) + 9) / 12) / 4
- 3 * ((i64::from(year) + (i64::from(month) - 9) / 7) / 100 + 1) / 4
+ 275 * i64::from(month) / 9
+ i64::from(day)
+ 1_721_028
- 2_400_000;
let frac_days = f64::from(hour) / f64::from(consts::DAY_HOURS)
+ f64::from(minute) / f64::from(consts::DAY_HOURS * consts::HOUR_MINUTES)
+ seconds / f64::from(consts::DAY_SECS);
MJD(full_days as f64 + frac_days)
}
#[must_use]
pub fn as_f64(&self) -> f64 {
self.0
}
pub(super) fn to_gps_internal(self, params: Option<&UtcParams>) -> GpsTime {
let utc_days: f64 = self.0 - f64::from(consts::MJD_JAN_6_1980);
let wn = (utc_days / f64::from(consts::WEEK_DAYS)) as i16;
let tow =
(utc_days - f64::from(wn) * f64::from(consts::WEEK_DAYS)) * f64::from(consts::DAY_SECS);
let utc_time = GpsTime::new_unchecked(wn, tow);
let leap_secs = params.map_or_else(
|| utc_time.utc_gps_offset_hardcoded(),
|p| utc_time.utc_gps_offset(p),
);
let gps_time = if leap_secs >= 0.0 {
utc_time - Duration::from_secs_f64(leap_secs)
} else {
utc_time + Duration::from_secs_f64(-leap_secs)
};
assert!(gps_time.is_valid());
gps_time
}
#[must_use]
pub fn to_gps(self, utc_params: &UtcParams) -> GpsTime {
self.to_gps_internal(Some(utc_params))
}
#[must_use]
pub fn to_gps_hardcoded(self) -> GpsTime {
self.to_gps_internal(None)
}
#[must_use]
pub fn to_utc(self) -> UtcTime {
let utc_days: f64 = self.0 - f64::from(consts::MJD_JAN_6_1980);
let wn = (utc_days / f64::from(consts::WEEK_DAYS)) as i16;
let tow =
(utc_days - f64::from(wn as u32 * consts::WEEK_DAYS)) * f64::from(consts::DAY_SECS);
let utc_time = GpsTime::new_unchecked(wn, tow);
UtcTime::from_gps_no_leap(utc_time)
}
#[must_use]
pub fn to_date(self) -> (u16, u8, u8, u8, u8, f64) {
let j = (self.0 as i32) + 2_400_001 + 68569;
let c = 4 * j / 146_097;
let j = j - (146_097 * c + 3) / 4;
let y = 4000 * (j + 1) / 1_461_001;
let j = j - 1461 * y / 4 + 31;
let m = 80 * j / 2447;
let day: u8 = (j - 2447 * m / 80) as u8;
let j = m / 11;
let month: u8 = (m + 2 - (12 * j)) as u8;
let year: u16 = (100 * (c - 49) + y + j) as u16;
let frac_part = self.0.fract();
let hour: u8 = (frac_part * f64::from(consts::DAY_HOURS)) as u8;
let min: u8 = ((frac_part - f64::from(hour) / f64::from(consts::DAY_HOURS))
* f64::from(consts::DAY_HOURS)
* f64::from(consts::HOUR_MINUTES)) as u8;
let sec: f64 = (frac_part
- f64::from(hour) / f64::from(consts::DAY_HOURS)
- f64::from(min) / f64::from(consts::DAY_HOURS) / f64::from(consts::HOUR_MINUTES))
* f64::from(consts::DAY_SECS);
(year, month, day, hour, min, sec)
}
}
impl From<UtcTime> for MJD {
fn from(utc: UtcTime) -> MJD {
utc.to_mjd()
}
}