use super::TimeScale;
use serde::{Deserialize, Serialize};
use anyhow::{bail, Result};
const MDAYS: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
#[derive(Copy, Clone, Serialize, Deserialize)]
pub struct Instant {
pub raw: i64,
}
mod gregorian_coefficients {
#[allow(non_upper_case_globals)]
pub const y: i64 = 4716;
#[allow(non_upper_case_globals)]
pub const j: i64 = 1401;
#[allow(non_upper_case_globals)]
pub const m: i64 = 2;
#[allow(non_upper_case_globals)]
pub const n: i64 = 12;
#[allow(non_upper_case_globals)]
pub const r: i64 = 4;
#[allow(non_upper_case_globals)]
pub const p: i64 = 1461;
#[allow(non_upper_case_globals)]
pub const v: i64 = 3;
#[allow(non_upper_case_globals)]
pub const u: i64 = 5;
#[allow(non_upper_case_globals)]
pub const s: i64 = 153;
#[allow(non_upper_case_globals)]
pub const t: i64 = 2;
#[allow(non_upper_case_globals)]
pub const w: i64 = 2;
pub const A: i64 = 184;
pub const B: i64 = 274_277;
pub const C: i64 = -38;
}
const LEAP_SECOND_TABLE: [(i64, i64); 28] = [
(1483228836000000, 37000000), (1435708835000000, 36000000), (1341100834000000, 35000000), (1230768033000000, 34000000), (1136073632000000, 33000000), (915148831000000, 32000000), (867715230000000, 31000000), (820454429000000, 30000000), (773020828000000, 29000000), (741484827000000, 28000000), (709948826000000, 27000000), (662688025000000, 26000000), (631152024000000, 25000000), (567993623000000, 24000000), (489024022000000, 23000000), (425865621000000, 22000000), (394329620000000, 21000000), (362793619000000, 20000000), (315532818000000, 19000000), (283996817000000, 18000000), (252460816000000, 17000000), (220924815000000, 16000000), (189302414000000, 15000000), (157766413000000, 14000000), (126230412000000, 13000000), (94694411000000, 12000000), (78796810000000, 11000000), (63072009000000, 10000000), ];
fn microleapseconds(raw: i64) -> i64 {
for (t, ls) in LEAP_SECOND_TABLE.iter() {
if raw > *t {
return *ls;
}
}
0
}
impl Instant {
pub const fn new(raw: i64) -> Self {
Self { raw }
}
pub fn from_gps_week_and_second(week: i32, sow: f64) -> Self {
let week = week as i64;
let raw = week * 604_800_000_000 + (sow * 1.0e6) as i64 + Self::GPS_EPOCH.raw;
Self { raw }
}
pub fn from_unixtime(unixtime: f64) -> Self {
let mut raw = (unixtime * 1.0e6) as i64 + Self::UNIX_EPOCH.raw;
let ls = microleapseconds(raw);
raw += ls;
raw += microleapseconds(raw) - ls;
Self { raw }
}
pub fn as_unixtime(&self) -> f64 {
(self.raw - Self::UNIX_EPOCH.raw - microleapseconds(self.raw)) as f64 * 1.0e-6
}
pub const J2000: Self = Self {
raw: 946727967816000,
};
pub const UNIX_EPOCH: Self = Self { raw: 0 };
pub const GPS_EPOCH: Self = Self {
raw: 315964819000000,
};
pub const INVALID: Self = Self { raw: i64::MIN };
pub const MJD_EPOCH: Self = Self {
raw: -3506716800000000,
};
pub fn day_of_week(&self) -> super::Weekday {
let jd = self.as_jd_utc();
super::Weekday::from(((jd + 1.5) % 7.0).floor() as i32)
}
#[deprecated(note = "Use as_mjd_utc() for explicit scale, or as_mjd_with_scale()")]
pub fn as_mjd(&self) -> f64 {
self.as_mjd_utc()
}
pub fn as_mjd_utc(&self) -> f64 {
self.as_mjd_with_scale(TimeScale::UTC)
}
#[deprecated(note = "Use from_mjd_utc() for explicit scale, or from_mjd_with_scale()")]
pub fn from_mjd(mjd: f64) -> Self {
Self::from_mjd_utc(mjd)
}
pub fn from_mjd_utc(mjd: f64) -> Self {
Self::from_mjd_with_scale(mjd, TimeScale::UTC)
}
#[deprecated(note = "Use from_jd_utc() for explicit scale, or from_jd_with_scale()")]
pub fn from_jd(jd: f64) -> Self {
Self::from_jd_utc(jd)
}
pub fn from_jd_utc(jd: f64) -> Self {
Self::from_mjd_utc(jd - 2400000.5)
}
pub fn from_jd_with_scale(jd: f64, scale: TimeScale) -> Self {
Self::from_mjd_with_scale(jd - 2400000.5, scale)
}
pub fn from_mjd_with_scale(mjd: f64, scale: TimeScale) -> Self {
match scale {
TimeScale::UTC => {
let raw = (mjd * 86_400_000_000.0) as i64 + Self::MJD_EPOCH.raw;
let ls = microleapseconds(raw);
let raw = raw + ls;
let raw = raw + microleapseconds(raw) - ls;
Self { raw }
}
TimeScale::TAI => {
let raw = (mjd * 86_400_000_000.0) as i64 + Self::MJD_EPOCH.raw;
Self { raw }
}
TimeScale::TT => {
let raw = (mjd * 86_400_000_000.0) as i64 + Self::MJD_EPOCH.raw - 32_184_000;
Self { raw }
}
TimeScale::UT1 => {
let eop =
crate::earth_orientation_params::eop_from_mjd_utc(mjd).unwrap_or([0.0; 6]);
let dut1 = eop[0] as f64;
Self::from_mjd_with_scale(mjd - dut1 / 86_400.0, TimeScale::UTC)
}
TimeScale::GPS => {
let raw = (mjd * 86_400_000_000.0) as i64 + Self::MJD_EPOCH.raw + 19_000_000;
Self { raw }
}
TimeScale::Invalid => Self::INVALID,
TimeScale::TDB => {
let ttc: f64 = (mjd - (2451545.0 - 2400000.5)) / 36525.0;
let mjd = (0.001657f64 / 86400.0f64).mul_add(
-(std::f64::consts::PI / 180.0 * 628.3076f64.mul_add(ttc, 6.2401)).sin(),
mjd,
) - 32.184 / 86400.0;
Self::from_mjd_with_scale(mjd, TimeScale::TAI)
}
}
}
#[deprecated(note = "Use as_jd_utc() for explicit scale, or as_jd_with_scale()")]
#[allow(deprecated)]
pub fn as_jd(&self) -> f64 {
self.as_mjd() + 2400000.5
}
pub fn as_jd_utc(&self) -> f64 {
self.as_mjd_utc() + 2400000.5
}
pub fn as_jd_with_scale(&self, scale: TimeScale) -> f64 {
self.as_mjd_with_scale(scale) + 2400000.5
}
pub fn add_utc_days(&self, days: f64) -> Self {
let mut utc = self.as_mjd_with_scale(TimeScale::UTC);
utc += days;
Self::from_mjd_with_scale(utc, TimeScale::UTC)
}
pub fn as_mjd_with_scale(&self, scale: TimeScale) -> f64 {
match scale {
TimeScale::UTC => {
(self.raw - Self::MJD_EPOCH.raw - microleapseconds(self.raw)) as f64
/ 86_400_000_000.0
}
TimeScale::TT => {
(self.raw - Self::MJD_EPOCH.raw + 32_184_000) as f64 / 86_400_000_000.0
}
TimeScale::UT1 => {
let mjd_utc = self.as_mjd_utc();
let eop =
crate::earth_orientation_params::eop_from_mjd_utc(mjd_utc).unwrap_or([0.0; 6]);
let dut1 = eop[0] as f64;
mjd_utc + dut1 / 86_400.0
}
TimeScale::TAI => (self.raw - Self::MJD_EPOCH.raw) as f64 / 86_400_000_000.0,
TimeScale::GPS => {
(self.raw - Self::MJD_EPOCH.raw - 19_000_000) as f64 / 86_400_000_000.0
}
TimeScale::TDB => {
let tt: f64 = self.as_mjd_with_scale(TimeScale::TT);
let ttc: f64 = (tt - (2451545.0f64 - 2400000.5f64)) / 36525.0;
(0.001657f64 / 86400.0f64).mul_add(
(std::f64::consts::PI / 180.0 * 628.3076f64.mul_add(ttc, 6.2401)).sin(),
tt,
)
}
TimeScale::Invalid => 0.0,
}
}
pub fn as_datetime(&self) -> (i32, i32, i32, i32, i32, f64) {
let utc_usec_of_day = (self.raw - microleapseconds(self.raw)) % 86_400_000_000;
let mut jdadd: i64 = 0;
let mut hour = utc_usec_of_day / 3_600_000_000;
if hour < 12 {
jdadd += 1
}
let mut minute = (utc_usec_of_day - (hour * 3_600_000_000)) / 60_000_000;
let mut second =
(utc_usec_of_day - (hour * 3_600_000_000) - (minute * 60_000_000)) as f64 * 1.0e-6;
for (t, _) in LEAP_SECOND_TABLE.iter() {
if self.raw >= *t && self.raw - *t < 1_000_000 {
hour = 23;
minute = 59;
if second == 0.0 {
second = 60.0;
jdadd -= 1;
} else {
second += 1.0;
}
}
}
use gregorian_coefficients as gc;
let mut jd = self.as_jd_utc().floor() as i64;
jd += jdadd;
let f = jd + gc::j + (((4 * jd + gc::B) / 146097) * 3) / 4 + gc::C;
let e = gc::r * f + gc::v;
let g = (e % gc::p) / gc::r;
let h = gc::u * g + gc::w;
let day = ((h % gc::s) / gc::u) + 1;
let month = ((h / gc::s + gc::m) % gc::n) + 1;
let year = (e / gc::p) - gc::y + (gc::n + gc::m - month) / gc::n;
(
year as i32,
month as i32,
day as i32,
hour as i32,
minute as i32,
second,
)
}
pub fn from_date(year: i32, month: i32, day: i32) -> Result<Self> {
Self::from_datetime(year, month, day, 0, 0, 0.0)
}
pub fn day_of_year(&self) -> u32 {
let (y, m, d, _, _, _) = self.as_datetime();
let leap = (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
let mut doy = d;
for mm in 1..m {
doy += if mm == 2 && leap {
29i32
} else {
MDAYS[(mm - 1) as usize] as i32
};
}
doy as u32
}
pub fn utc(
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: f64,
) -> Result<Self> {
Self::from_datetime(year, month, day, hour, minute, second)
}
pub fn from_datetime(
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: f64,
) -> Result<Self> {
let mut check_leapsecond: bool = false;
if !(1..=12).contains(&month) {
bail!("Invalid month: {}", month);
}
let max_day = if month == 2 {
if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
29
} else {
28
}
} else {
MDAYS[(month - 1) as usize]
};
if day < 1 || day > max_day as i32 {
bail!("Invalid day: {}", day);
}
if !(0..=23).contains(&hour) {
bail!("Invalid hour: {}", hour);
}
if !(0..=59).contains(&minute) {
bail!("Invalid minute: {}", minute);
}
if !(0.0..60.0).contains(&second) {
if (60.0..61.0).contains(&second) {
check_leapsecond = true;
} else {
bail!("Invalid second: {}", second);
}
}
use gregorian_coefficients as gc;
let h = month as i64 - gc::m;
let g = year as i64 + gc::y - (gc::n - h) / gc::n;
let f = (h - 1 + gc::n) % gc::n;
let e = (gc::p * g) / gc::r + day as i64 - 1 - gc::j;
let mut jd = e + (gc::s * f + gc::t) / gc::u;
jd = jd - (3 * ((g + gc::A) / 100)) / 4 - gc::C;
let jd = jd as f64 - 0.5;
let mjd = jd - 2400000.5;
let mut raw = mjd as i64 * 86_400_000_000
+ (hour as i64 * 3_600_000_000)
+ (minute as i64 * 60_000_000)
+ (second * 1_000_000.0) as i64
+ Self::MJD_EPOCH.raw;
let ls = microleapseconds(raw);
raw += ls;
raw = raw + microleapseconds(raw) - ls;
if check_leapsecond {
let mut valid_leap_second = false;
for (t, _) in LEAP_SECOND_TABLE.iter() {
if raw >= *t + 1_000_000 && raw < *t + 2_000_000 {
valid_leap_second = true;
break;
}
}
if !valid_leap_second {
bail!("Invalid second");
}
}
Ok(Self { raw })
}
pub fn now() -> Self {
let now = std::time::SystemTime::now();
let since_epoch = now.duration_since(std::time::UNIX_EPOCH).unwrap();
let mut raw = since_epoch.as_micros() as i64;
let ls = microleapseconds(raw);
raw += ls;
raw += microleapseconds(raw) - ls;
Self { raw }
}
}
impl std::fmt::Display for Instant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (year, month, day, hour, minute, second) = self.as_datetime();
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:09.6}Z",
year, month, day, hour, minute, second
)
}
}
impl std::fmt::Debug for Instant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (year, month, day, hour, minute, second) = self.as_datetime();
write!(
f,
"Instant {{ year: {}, month: {}, day: {}, hour: {}, minute: {}, second: {:06.3} }}",
year, month, day, hour, minute, second
)
}
}