extern crate regex;
extern crate serde;
extern crate serde_derive;
use self::regex::Regex;
use self::serde::{de, Deserialize, Deserializer};
use crate::duration::{Duration, TimeUnit};
use crate::{
Errors, TimeSystem, DAYS_PER_CENTURY, ET_EPOCH_S, J1900_OFFSET, MJD_OFFSET, SECONDS_PER_DAY,
};
use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::str::FromStr;
const TT_OFFSET_S: f64 = 32.184;
const ET_OFFSET_S: f64 = 32.184_935;
const LEAP_SECONDS: [f64; 28] = [
2_272_060_800.0,
2_287_785_600.0,
2_303_683_200.0,
2_335_219_200.0,
2_366_755_200.0,
2_398_291_200.0,
2_429_913_600.0,
2_461_449_600.0,
2_492_985_600.0,
2_524_521_600.0,
2_571_782_400.0,
2_603_318_400.0,
2_634_854_400.0,
2_698_012_800.0,
2_776_982_400.0,
2_840_140_800.0,
2_871_676_800.0,
2_918_937_600.0,
2_950_473_600.0,
2_982_009_600.0,
3_029_443_200.0,
3_076_704_000.0,
3_124_137_600.0,
3_345_062_400.0,
3_439_756_800.0,
3_550_089_600.0,
3_644_697_600.0,
3_692_217_600.0,
];
const JANUARY_YEARS: [i32; 17] = [
1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1988, 1990, 1991, 1996, 1999, 2006, 2009,
2017,
];
const JULY_YEARS: [i32; 11] = [
1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015,
];
const USUAL_DAYS_PER_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Epoch(Duration);
impl Sub for Epoch {
type Output = Duration;
fn sub(self, other: Self) -> Duration {
self.0 - other.0
}
}
impl SubAssign<Duration> for Epoch {
fn sub_assign(&mut self, duration: Duration) {
*self = *self - duration;
}
}
impl Sub<Duration> for Epoch {
type Output = Self;
fn sub(self, duration: Duration) -> Self {
Self {
0: self.0 - duration,
}
}
}
impl Add<f64> for Epoch {
type Output = Self;
fn add(self, seconds: f64) -> Self {
Self {
0: (self.0.in_seconds() + seconds) * TimeUnit::Second,
}
}
}
impl Add<Duration> for Epoch {
type Output = Self;
fn add(self, duration: Duration) -> Self {
Self {
0: self.0 + duration,
}
}
}
impl AddAssign<TimeUnit> for Epoch {
#[allow(clippy::identity_op)]
fn add_assign(&mut self, unit: TimeUnit) {
*self = *self + unit * 1;
}
}
impl SubAssign<TimeUnit> for Epoch {
#[allow(clippy::identity_op)]
fn sub_assign(&mut self, unit: TimeUnit) {
*self = *self - unit * 1;
}
}
impl Sub<TimeUnit> for Epoch {
type Output = Self;
#[allow(clippy::identity_op)]
fn sub(self, unit: TimeUnit) -> Self {
Self {
0: self.0 - unit * 1,
}
}
}
impl Add<TimeUnit> for Epoch {
type Output = Self;
#[allow(clippy::identity_op)]
fn add(self, unit: TimeUnit) -> Self {
Self {
0: self.0 + unit * 1,
}
}
}
impl AddAssign<Duration> for Epoch {
fn add_assign(&mut self, duration: Duration) {
*self = *self + duration;
}
}
impl Epoch {
pub fn from_tai_seconds(seconds: f64) -> Self {
assert!(
seconds.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self {
0: seconds * TimeUnit::Second,
}
}
pub fn from_tai_days(days: f64) -> Self {
assert!(
days.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self {
0: days * TimeUnit::Day,
}
}
pub fn from_mjd_tai(days: f64) -> Self {
assert!(
days.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self {
0: (days - J1900_OFFSET) * TimeUnit::Day,
}
}
pub fn from_jde_tai(days: f64) -> Self {
assert!(
days.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self {
0: (days - J1900_OFFSET - MJD_OFFSET) * TimeUnit::Day,
}
}
pub fn from_tt_seconds(seconds: f64) -> Self {
assert!(
seconds.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self::from_tai_seconds(seconds) - TimeUnit::Second * TT_OFFSET_S
}
pub fn from_et_seconds(seconds: f64) -> Epoch {
assert!(
seconds.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self::from_tai_seconds(seconds) + TimeUnit::Second * (ET_EPOCH_S - ET_OFFSET_S)
}
pub fn from_tdb_seconds(seconds: f64) -> Epoch {
assert!(
seconds.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self::from_tdb_seconds_d(seconds * TimeUnit::Second)
}
fn from_tdb_seconds_d(duration: Duration) -> Epoch {
use std::f64::consts::PI;
let tt_duration = duration - TimeUnit::Second * TT_OFFSET_S;
let tt_centuries_j2k =
(tt_duration - TimeUnit::Second * ET_EPOCH_S).in_unit_f64(TimeUnit::Century);
let g_rad = (PI / 180.0) * (357.528 + 35_999.050 * tt_centuries_j2k);
let inner = g_rad + 0.0167 * g_rad.sin();
Self {
0: tt_duration + (ET_EPOCH_S - (0.001_658 * inner.sin())) * TimeUnit::Second,
}
}
pub fn from_jde_et(days: f64) -> Self {
assert!(
days.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self::from_jde_tdb(days)
}
pub fn from_jde_tdb(days: f64) -> Self {
assert!(
days.is_finite(),
"Attempted to initialize Epoch with non finite number"
);
Self::from_jde_tai(days) - TimeUnit::Second * ET_OFFSET_S
}
pub fn maybe_from_gregorian_tai(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> Result<Self, Errors> {
Self::maybe_from_gregorian(
year,
month,
day,
hour,
minute,
second,
nanos,
TimeSystem::TAI,
)
}
#[allow(clippy::too_many_arguments)]
pub fn maybe_from_gregorian(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
ts: TimeSystem,
) -> Result<Self, Errors> {
if !is_gregorian_valid(year, month, day, hour, minute, second, nanos) {
return Err(Errors::Carry);
}
let mut seconds_wrt_1900 = TimeUnit::Day * (365 * (year - 1900).abs());
for year in 1900..year {
if is_leap_year(year) {
seconds_wrt_1900 += TimeUnit::Day;
}
}
for month in 0..month - 1 {
seconds_wrt_1900 += TimeUnit::Day * USUAL_DAYS_PER_MONTH[(month) as usize];
}
if is_leap_year(year) && month > 2 {
seconds_wrt_1900 += TimeUnit::Day;
}
seconds_wrt_1900 += TimeUnit::Day * (day - 1)
+ TimeUnit::Hour * hour
+ TimeUnit::Minute * minute
+ TimeUnit::Second * second
+ TimeUnit::Nanosecond * nanos;
if second == 60 {
seconds_wrt_1900 -= TimeUnit::Second;
}
Ok(match ts {
TimeSystem::TAI => Self {
0: seconds_wrt_1900,
},
TimeSystem::TT => Self {
0: (seconds_wrt_1900 - TimeUnit::Second * TT_OFFSET_S),
},
TimeSystem::ET => Self {
0: (seconds_wrt_1900 + TimeUnit::Second * (ET_EPOCH_S - ET_OFFSET_S)),
},
TimeSystem::TDB => Self::from_tdb_seconds_d(seconds_wrt_1900),
TimeSystem::UTC => panic!("use maybe_from_gregorian_utc for UTC time system"),
})
}
pub fn from_gregorian_tai(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> Self {
Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_tai_at_midnight(year: i32, month: u8, day: u8) -> Self {
Self::maybe_from_gregorian_tai(year, month, day, 0, 0, 0, 0)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_tai_at_noon(year: i32, month: u8, day: u8) -> Self {
Self::maybe_from_gregorian_tai(year, month, day, 12, 0, 0, 0)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_tai_hms(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> Self {
Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, 0)
.expect("invalid Gregorian date")
}
pub fn maybe_from_gregorian_utc(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> Result<Self, Errors> {
let mut if_tai =
Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)?;
let mut cnt = 0;
for tai_ts in LEAP_SECONDS.iter() {
if if_tai.0.in_seconds() >= *tai_ts {
if cnt == 0 {
cnt = 10;
} else {
cnt += 1;
}
} else {
break;
}
}
if_tai.0 += cnt * TimeUnit::Second;
Ok(if_tai)
}
pub fn from_gregorian_utc(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> Self {
Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, nanos)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_utc_at_midnight(year: i32, month: u8, day: u8) -> Self {
Self::maybe_from_gregorian_utc(year, month, day, 0, 0, 0, 0)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_utc_at_noon(year: i32, month: u8, day: u8) -> Self {
Self::maybe_from_gregorian_utc(year, month, day, 12, 0, 0, 0)
.expect("invalid Gregorian date")
}
pub fn from_gregorian_utc_hms(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> Self {
Self::maybe_from_gregorian_utc(year, month, day, hour, minute, second, 0)
.expect("invalid Gregorian date")
}
pub fn as_tai_seconds(self) -> f64 {
self.0.in_seconds()
}
pub fn as_tai_duration(self) -> Duration {
self.0
}
pub fn as_tai(self, unit: TimeUnit) -> f64 {
self.0.in_unit_f64(unit)
}
pub fn as_tai_days(self) -> f64 {
self.as_tai(TimeUnit::Day)
}
pub fn as_utc_seconds(self) -> f64 {
self.as_utc(TimeUnit::Second)
}
fn as_utc_duration(self) -> Duration {
let mut cnt = 0;
for tai_ts in LEAP_SECONDS.iter() {
if self.0.in_seconds() >= *tai_ts {
if cnt == 0 {
cnt = 10;
} else {
cnt += 1;
}
} else {
break;
}
}
self.0 + (-cnt) * TimeUnit::Second
}
pub fn as_utc(self, unit: TimeUnit) -> f64 {
self.as_utc_duration().in_unit_f64(unit)
}
pub fn as_utc_days(self) -> f64 {
self.as_utc(TimeUnit::Day)
}
pub fn as_mjd_tai_days(self) -> f64 {
self.as_mjd_tai(TimeUnit::Day)
}
pub fn as_mjd_tai_seconds(self) -> f64 {
self.as_mjd_tai(TimeUnit::Second)
}
pub fn as_mjd_tai(self, unit: TimeUnit) -> f64 {
(self.0 + TimeUnit::Day * J1900_OFFSET).in_unit_f64(unit)
}
pub fn as_mjd_utc_days(self) -> f64 {
self.as_mjd_utc(TimeUnit::Day)
}
pub fn as_mjd_utc(self, unit: TimeUnit) -> f64 {
(self.as_utc_duration() + TimeUnit::Day * J1900_OFFSET).in_unit_f64(unit)
}
pub fn as_mjd_utc_seconds(self) -> f64 {
self.as_mjd_utc(TimeUnit::Second)
}
pub fn as_jde_tai_days(self) -> f64 {
self.as_jde_tai(TimeUnit::Day)
}
pub fn as_jde_tai(self, unit: TimeUnit) -> f64 {
self.as_jde_tai_duration().in_unit_f64(unit)
}
pub fn as_jde_tai_duration(self) -> Duration {
self.0 + TimeUnit::Day * J1900_OFFSET + TimeUnit::Day * MJD_OFFSET
}
pub fn as_jde_tai_seconds(self) -> f64 {
self.as_jde_tai(TimeUnit::Second)
}
pub fn as_jde_utc_days(self) -> f64 {
self.as_jde_utc_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_jde_utc_duration(self) -> Duration {
self.as_utc_duration() + TimeUnit::Day * (J1900_OFFSET + MJD_OFFSET)
}
pub fn as_jde_utc_seconds(self) -> f64 {
self.as_jde_utc_duration().in_seconds()
}
pub fn as_tt_seconds(self) -> f64 {
self.as_tai_seconds() + TT_OFFSET_S
}
pub fn as_tt_duration(self) -> Duration {
self.0 + TimeUnit::Second * TT_OFFSET_S
}
pub fn as_tt_days(self) -> f64 {
self.as_tt_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_tt_centuries_j2k(self) -> f64 {
(self.as_tt_seconds() - ET_EPOCH_S) / (DAYS_PER_CENTURY * SECONDS_PER_DAY)
}
pub fn as_tt_since_j2k(self) -> Duration {
self.as_tt_duration() - TimeUnit::Second * ET_EPOCH_S
}
pub fn as_jde_tt_days(self) -> f64 {
self.as_jde_tt_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_jde_tt_duration(self) -> Duration {
self.as_tt_duration() + TimeUnit::Day * (J1900_OFFSET + MJD_OFFSET)
}
pub fn as_mjd_tt_days(self) -> f64 {
self.as_mjd_tt_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_mjd_tt_duration(self) -> Duration {
self.as_tt_duration() + TimeUnit::Day * J1900_OFFSET
}
pub fn as_gpst_seconds(self) -> f64 {
self.as_gpst_duration().in_seconds()
}
pub fn as_gpst_duration(self) -> Duration {
self.as_tai_duration() - TimeUnit::Second * 19.0
}
pub fn as_gpst_days(self) -> f64 {
self.as_gpst_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_et_seconds(self) -> f64 {
self.as_et_duration().in_seconds()
}
pub fn as_et_duration(self) -> Duration {
self.as_tai_duration() - TimeUnit::Second * (ET_EPOCH_S - ET_OFFSET_S)
}
pub fn as_tdb_seconds(self) -> f64 {
let inner = self.inner_g_rad();
self.as_tt_seconds() - ET_EPOCH_S + (0.001_658 * inner.sin())
}
fn inner_g_rad(&self) -> f64 {
use std::f64::consts::PI;
let g_rad = (PI / 180.0) * (357.528 + 35_999.050 * self.as_tt_centuries_j2k());
g_rad + 0.0167 * g_rad.sin()
}
pub fn as_tdb_duration(self) -> Duration {
let inner = self.inner_g_rad();
self.as_tt_duration() - TimeUnit::Second * (ET_EPOCH_S + (0.001_658 * inner.sin()))
}
pub fn as_jde_et_days(self) -> f64 {
self.as_jde_et_duration().in_unit_f64(TimeUnit::Day)
}
pub fn as_jde_et_duration(self) -> Duration {
self.as_jde_tt_duration() + TimeUnit::Second * 0.000_935
}
pub fn as_jde_et(self, unit: TimeUnit) -> f64 {
self.as_jde_et_duration().in_unit_f64(unit)
}
pub fn as_jde_tdb_duration(self) -> Duration {
self.as_jde_tdb_days() * TimeUnit::Day
}
pub fn as_jde_tdb_days(self) -> f64 {
let inner = self.inner_g_rad();
let tdb_delta = 0.001_658 * inner.sin();
self.as_jde_tt_days() + tdb_delta / SECONDS_PER_DAY
}
pub fn from_gregorian_str(s: &str) -> Result<Self, Errors> {
let reg: Regex = Regex::new(
r"^(\d{4})-(\d{2})-(\d{2})(?:T|\W)(\d{2}):(\d{2}):(\d{2})\.?(\d+)?\W?(\w{2,3})?$",
)
.unwrap();
match reg.captures(s) {
Some(cap) => {
let nanos = match cap.get(7) {
Some(val) => val.as_str().parse::<u32>().unwrap(),
None => 0,
};
match cap.get(8) {
Some(ts_str) => {
let ts = TimeSystem::map(ts_str.as_str().to_owned());
if ts == TimeSystem::UTC {
Self::maybe_from_gregorian_utc(
cap[1].to_owned().parse::<i32>()?,
cap[2].to_owned().parse::<u8>()?,
cap[3].to_owned().parse::<u8>()?,
cap[4].to_owned().parse::<u8>()?,
cap[5].to_owned().parse::<u8>()?,
cap[6].to_owned().parse::<u8>()?,
nanos,
)
} else {
Self::maybe_from_gregorian(
cap[1].to_owned().parse::<i32>()?,
cap[2].to_owned().parse::<u8>()?,
cap[3].to_owned().parse::<u8>()?,
cap[4].to_owned().parse::<u8>()?,
cap[5].to_owned().parse::<u8>()?,
cap[6].to_owned().parse::<u8>()?,
nanos,
ts,
)
}
}
None => {
Self::maybe_from_gregorian_utc(
cap[1].to_owned().parse::<i32>()?,
cap[2].to_owned().parse::<u8>()?,
cap[3].to_owned().parse::<u8>()?,
cap[4].to_owned().parse::<u8>()?,
cap[5].to_owned().parse::<u8>()?,
cap[6].to_owned().parse::<u8>()?,
nanos,
)
}
}
}
None => Err(Errors::ParseError(
"Input not in ISO8601 format without offset (e.g. 2018-01-27T00:41:55)".to_owned(),
)),
}
}
pub fn as_gregorian_utc(self) -> (i32, u8, u8, u8, u8, u8, u32) {
Self::compute_gregorian(self.as_utc_seconds())
}
pub fn as_gregorian_utc_str(self) -> String {
self.as_gregorian_str(TimeSystem::UTC)
}
pub fn as_gregorian_tai(self) -> (i32, u8, u8, u8, u8, u8, u32) {
Self::compute_gregorian(self.as_tai_seconds())
}
pub fn as_gregorian_tai_str(self) -> String {
self.as_gregorian_str(TimeSystem::TAI)
}
pub fn as_gregorian_str(self, ts: TimeSystem) -> String {
let (y, mm, dd, hh, min, s, nanos) = Self::compute_gregorian(match ts {
TimeSystem::ET => self.as_et_seconds(),
TimeSystem::TT => self.as_tt_seconds(),
TimeSystem::TAI => self.as_tai_seconds(),
TimeSystem::TDB => self.as_tdb_seconds(),
TimeSystem::UTC => self.as_utc_seconds(),
});
if nanos == 0 {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02} {:?}",
y, mm, dd, hh, min, s, ts
)
} else {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{} {:?}",
y, mm, dd, hh, min, s, nanos, ts
)
}
}
fn compute_gregorian(absolute_seconds: f64) -> (i32, u8, u8, u8, u8, u8, u32) {
let (mut year, mut year_fraction) = quorem(absolute_seconds, 365.0 * SECONDS_PER_DAY);
year += 1900;
for year in 1900..year {
if is_leap_year(year) {
year_fraction -= SECONDS_PER_DAY;
}
}
let mut seconds_til_this_month = 0.0;
let mut month = 1;
if year_fraction < 0.0 {
month = 12;
year -= 1;
} else {
loop {
seconds_til_this_month +=
SECONDS_PER_DAY * f64::from(USUAL_DAYS_PER_MONTH[(month - 1) as usize]);
if is_leap_year(year) && month == 2 {
seconds_til_this_month += SECONDS_PER_DAY;
}
if seconds_til_this_month > year_fraction {
break;
}
month += 1;
}
}
let mut days_this_month = USUAL_DAYS_PER_MONTH[(month - 1) as usize];
if month == 2 && is_leap_year(year) {
days_this_month += 1;
}
let (_, month_fraction) = quorem(
year_fraction - seconds_til_this_month,
f64::from(days_this_month) * SECONDS_PER_DAY,
);
let (mut day, day_fraction) = quorem(month_fraction, SECONDS_PER_DAY);
if day < 0 {
month -= 1;
if month == 0 {
month = 12;
year -= 1;
}
day = i32::from(USUAL_DAYS_PER_MONTH[(month - 1) as usize]);
}
day += 1;
let (hours, hours_fraction) = quorem(day_fraction, 60.0 * 60.0);
let (mins, secs) = quorem(hours_fraction, 60.0);
let nanos = (quorem(secs, 1.0).1 * 1e9) as u32;
(
year,
month as u8,
day as u8,
hours as u8,
mins as u8,
secs as u8,
nanos,
)
}
}
impl FromStr for Epoch {
type Err = Errors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let reg: Regex = Regex::new(r"^(\w{2,3})\W?(\d+\.?\d+)\W?(\w{2,3})?$").unwrap();
match Self::from_gregorian_str(s) {
Ok(e) => Ok(e),
Err(_) => match reg.captures(s) {
Some(cap) => {
let format = cap[1].to_owned().parse::<String>().unwrap();
let value = cap[2].to_owned().parse::<f64>().unwrap();
let ts = TimeSystem::map(cap[3].to_owned().parse::<String>().unwrap());
match format.as_str() {
"JD" => match ts {
TimeSystem::ET => Ok(Self::from_jde_et(value)),
TimeSystem::TAI => Ok(Self::from_jde_tai(value)),
TimeSystem::TDB => Ok(Self::from_jde_tdb(value)),
_ => Err(Errors::ParseError(format!(
"Cannot initialize JD in {:?}",
ts
))),
},
"MJD" => match ts {
TimeSystem::TAI => Ok(Self::from_mjd_tai(value)),
_ => Err(Errors::ParseError(format!(
"Cannot initialize MJD in {:?}",
ts
))),
},
"SEC" => match ts {
TimeSystem::TAI => Ok(Self::from_tai_seconds(value)),
TimeSystem::ET => Ok(Self::from_et_seconds(value)),
TimeSystem::TDB => Ok(Self::from_tdb_seconds(value)),
TimeSystem::TT => Ok(Self::from_tt_seconds(value)),
_ => Err(Errors::ParseError(format!(
"Cannot initialize SEC in {:?}",
ts
))),
},
_ => Err(Errors::ParseError(format!("Unknown format {}", format))),
}
}
None => Err(Errors::ParseError("Input not understood".to_owned())),
},
}
}
}
impl<'de> Deserialize<'de> for Epoch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}
impl fmt::Display for Epoch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_gregorian_tai_str())
}
}
pub fn is_gregorian_valid(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> bool {
let max_seconds = if (month == 12 || month == 6)
&& day == USUAL_DAYS_PER_MONTH[month as usize - 1]
&& hour == 23
&& minute == 59
&& ((month == 6 && JULY_YEARS.contains(&year))
|| (month == 12 && JANUARY_YEARS.contains(&(year + 1))))
{
60
} else {
59
};
if month == 0
|| month > 12
|| day == 0
|| day > 31
|| hour > 24
|| minute > 59
|| second > max_seconds
|| f64::from(nanos) > 1e9
{
return false;
}
if day > USUAL_DAYS_PER_MONTH[month as usize - 1] && (month != 2 || !is_leap_year(year)) {
return false;
}
true
}
fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
#[allow(clippy::float_equality_without_abs)]
#[test]
fn utc_epochs() {
use std::f64::EPSILON;
assert!(Epoch::from_mjd_tai(J1900_OFFSET).as_tai_seconds() < EPSILON);
assert!((Epoch::from_mjd_tai(J1900_OFFSET).as_mjd_tai_days() - J1900_OFFSET).abs() < EPSILON);
let this_epoch = Epoch::from_tai_seconds(1_199_333_568.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 1, 3, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_156_800.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 2, 28, 00, 00, 00, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_243_199.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 2, 28, 23, 59, 59, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_243_200.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 1, 00, 00, 00, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_206_850_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_214_194_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 6, 24, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_220_069_568.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 8, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_230_610_368.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 12, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_230_696_768.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 1, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_235_794_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 3, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_416_768.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 3, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_233_375_168.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 2, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_264_911_168.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_243_968.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 28, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_330_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 29, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let epoch_from_tai_secs = Epoch::from_gregorian_tai_at_midnight(1972, 1, 1);
assert!(epoch_from_tai_secs.as_tai_seconds() - 2_272_060_800.0 < EPSILON);
let epoch_from_tai_greg = Epoch::from_tai_seconds(2_272_060_800.0);
assert_eq!(epoch_from_tai_greg, epoch_from_tai_secs, "Incorrect epoch");
let epoch_from_utc_greg = Epoch::from_gregorian_utc_hms(1972, 6, 30, 23, 59, 59);
let epoch_from_utc_greg1 = Epoch::from_gregorian_utc_hms(1972, 7, 1, 0, 0, 0);
assert!(
(epoch_from_utc_greg1.as_tai_seconds() - epoch_from_utc_greg.as_tai_seconds() - 2.0).abs()
< EPSILON
);
let this_epoch = Epoch::from_tai_seconds(3_692_217_599.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 23);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
assert!(this_epoch.as_tai_seconds() - epoch_utc.as_utc_seconds() - 36.0 < EPSILON);
let this_epoch = Epoch::from_tai_seconds(3_692_217_600.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 24);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
assert!(this_epoch.as_tai_seconds() - epoch_utc.as_utc_seconds() - 37.0 < EPSILON);
let mut this_epoch = Epoch::from_tai_seconds(3_692_217_600.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 24);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
this_epoch += TimeUnit::Second * 3600.0;
assert_eq!(
this_epoch,
Epoch::from_gregorian_utc_hms(2017, 1, 1, 0, 59, 23),
"Incorrect epoch when adding an hour across leap second"
);
this_epoch -= TimeUnit::Hour;
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch after sub");
let this_epoch = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
assert!((this_epoch.as_jde_tai_days() - 2_458_849.5).abs() < std::f64::EPSILON)
}
#[allow(clippy::float_equality_without_abs)]
#[test]
fn utc_tai() {
use std::f64::EPSILON;
let flp_from_secs_tai = Epoch::from_tai_seconds(2_272_060_800.0);
let flp_from_greg_tai = Epoch::from_gregorian_tai_at_midnight(1972, 1, 1);
assert_eq!(flp_from_secs_tai, flp_from_greg_tai);
assert_eq!(
Epoch::from_gregorian_tai_hms(1972, 1, 1, 0, 0, 10),
Epoch::from_gregorian_utc_at_midnight(1972, 1, 1),
"UTC discontinuity failed"
);
assert!(
Epoch::from_gregorian_utc_at_noon(1972, 1, 1)
> Epoch::from_gregorian_tai_at_noon(1972, 1, 1),
"TAI is not ahead of UTC (via PartialEq) at noon after first leap second"
);
assert!(
flp_from_secs_tai.as_tai_seconds() > flp_from_secs_tai.as_utc_seconds(),
"TAI is not ahead of UTC (via function call)"
);
assert!(
(flp_from_secs_tai.as_tai_seconds() - flp_from_secs_tai.as_utc_seconds() - 10.0) < EPSILON,
"TAI is not ahead of UTC"
);
let epoch_utc = Epoch::from_gregorian_utc_hms(2019, 8, 1, 20, 10, 23);
let epoch_tai = Epoch::from_gregorian_tai_hms(2019, 8, 1, 20, 10, 23);
assert!(epoch_tai < epoch_utc, "TAI is not ahead of UTC");
let delta: Duration = epoch_utc - epoch_tai - TimeUnit::Second * 37.0;
assert!(delta < TimeUnit::Nanosecond, "TAI is not ahead of UTC");
assert!(
(epoch_utc.as_tai_seconds() - epoch_tai.as_tai_seconds() - 37.0).abs() < EPSILON,
"TAI is not ahead of UTC"
);
assert!(
(epoch_utc.as_utc_seconds() - epoch_tai.as_utc_seconds() - 37.0).abs() < EPSILON,
"TAI is not ahead of UTC"
);
}
#[test]
fn julian_epoch() {
use std::f64::EPSILON;
let nist_j1900 = Epoch::from_tai_days(0.0);
assert!((nist_j1900.as_mjd_tai_days() - 15_020.0).abs() < EPSILON);
assert!((nist_j1900.as_jde_tai_days() - 2_415_020.5).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 1);
assert!((mjd.as_mjd_tai_days() - 15_020.0).abs() < EPSILON);
let j1900 = Epoch::from_tai_days(0.5);
assert!((j1900.as_mjd_tai_days() - 15_020.5).abs() < EPSILON);
assert!((j1900.as_jde_tai_days() - 2_415_021.0).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_noon(1900, 1, 1);
assert!((mjd.as_mjd_tai_days() - 15_020.5).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 8);
assert!((mjd.as_mjd_tai_days() - 15_027.0).abs() < EPSILON);
assert!((mjd.as_jde_tai_days() - 2_415_027.5).abs() < EPSILON);
let gps_std_epoch = Epoch::from_gregorian_tai_at_midnight(1980, 1, 6);
assert!((gps_std_epoch.as_mjd_tai_days() - 44_244.0).abs() < EPSILON);
assert!((gps_std_epoch.as_jde_tai_days() - 2_444_244.5).abs() < EPSILON);
let j2000 = Epoch::from_gregorian_tai_at_midnight(2000, 1, 1);
assert!((j2000.as_mjd_tai_days() - 51_544.0).abs() < EPSILON);
assert!((j2000.as_jde_tai_days() - 2_451_544.5).abs() < EPSILON);
assert!(
Epoch::from_gregorian_tai_at_midnight(2000, 1, 1)
< Epoch::from_gregorian_utc_at_midnight(2000, 1, 1),
"TAI not ahead of UTC on J2k"
);
assert_eq!(
(Epoch::from_gregorian_utc_at_midnight(2000, 1, 1)
- Epoch::from_gregorian_tai_at_midnight(2000, 1, 1)),
TimeUnit::Second * 32.0
);
let j2000 = Epoch::from_gregorian_utc_at_midnight(2000, 1, 1);
assert!((j2000.as_mjd_utc_days() - 51_544.0).abs() < EPSILON);
assert!((j2000.as_jde_utc_days() - 2_451_544.5).abs() < EPSILON);
let jd020207 = Epoch::from_gregorian_tai_at_midnight(2002, 2, 7);
assert!((jd020207.as_mjd_tai_days() - 52_312.0).abs() < EPSILON);
assert!((jd020207.as_jde_tai_days() - 2_452_312.5).abs() < EPSILON);
assert!(
(Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 59).as_mjd_tai_days()
- 57_203.999_988_425_92)
.abs()
< EPSILON,
"Incorrect July 2015 leap second MJD computed"
);
assert!(
(Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 60).as_mjd_tai_days()
- 57_203.999_988_425_92)
.abs()
< EPSILON,
"Incorrect July 2015 leap second MJD computed"
);
assert!(
(Epoch::from_gregorian_tai_at_midnight(2015, 7, 1).as_mjd_tai_days() - 57_204.0).abs()
< EPSILON,
"Incorrect Post July 2015 leap second MJD computed"
);
}
#[test]
fn leap_year() {
assert!(!is_leap_year(2019));
assert!(!is_leap_year(2001));
assert!(!is_leap_year(1000));
let leap_years: [i32; 146] = [
1804, 1808, 1812, 1816, 1820, 1824, 1828, 1832, 1836, 1840, 1844, 1848, 1852, 1856, 1860,
1864, 1868, 1872, 1876, 1880, 1884, 1888, 1892, 1896, 1904, 1908, 1912, 1916, 1920, 1924,
1928, 1932, 1936, 1940, 1944, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984,
1988, 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044,
2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076, 2080, 2084, 2088, 2092, 2096, 2104, 2108,
2112, 2116, 2120, 2124, 2128, 2132, 2136, 2140, 2144, 2148, 2152, 2156, 2160, 2164, 2168,
2172, 2176, 2180, 2184, 2188, 2192, 2196, 2204, 2208, 2212, 2216, 2220, 2224, 2228, 2232,
2236, 2240, 2244, 2248, 2252, 2256, 2260, 2264, 2268, 2272, 2276, 2280, 2284, 2288, 2292,
2296, 2304, 2308, 2312, 2316, 2320, 2324, 2328, 2332, 2336, 2340, 2344, 2348, 2352, 2356,
2360, 2364, 2368, 2372, 2376, 2380, 2384, 2388, 2392, 2396, 2400,
];
for year in leap_years.iter() {
assert!(is_leap_year(*year));
}
}
#[test]
fn datetime_invalid_dates() {
assert!(!is_gregorian_valid(2001, 2, 29, 22, 8, 47, 0));
assert!(!is_gregorian_valid(2016, 12, 31, 23, 59, 61, 0));
assert!(!is_gregorian_valid(2015, 6, 30, 23, 59, 61, 0));
}
#[test]
fn gpst() {
use std::f64::EPSILON;
let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9);
assert!(
now.as_tai_seconds() > now.as_utc_seconds(),
"TAI is not ahead of UTC"
);
assert!((now.as_tai_seconds() - now.as_utc_seconds() - 37.0).abs() < EPSILON);
assert!(
now.as_tai_seconds() > now.as_gpst_seconds(),
"TAI is not head of GPS Time"
);
assert!((now.as_tai_seconds() - now.as_gpst_seconds() - 19.0).abs() < EPSILON);
assert!(
now.as_gpst_seconds() > now.as_utc_seconds(),
"GPS Time is not head of UTC"
);
assert!((now.as_gpst_seconds() - now.as_utc_seconds() - 18.0).abs() < EPSILON);
}
#[test]
fn spice_et_tdb() {
use crate::J2000_NAIF;
let sp_ex = Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33);
let expected_et_s = 381_885_819.184_935_87;
let from_et_s = Epoch::from_et_seconds(expected_et_s);
assert!(dbg!(from_et_s.as_et_seconds() - expected_et_s).abs() < std::f64::EPSILON);
assert!((sp_ex.as_et_seconds() - expected_et_s).abs() < 1e-6);
assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6);
assert!((sp_ex.as_jde_utc_days() - 2455964.9739931).abs() < 1e-7);
assert!(
dbg!(sp_ex.as_tai_seconds() - from_et_s.as_tai_seconds()).abs()
< 1e-6
);
let sp_ex = Epoch::from_gregorian_utc_at_midnight(2002, 2, 7);
let expected_et_s = 66_312_064.184_938_76;
assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6);
assert!(
(sp_ex.as_tai_seconds() - Epoch::from_et_seconds(expected_et_s).as_tai_seconds()).abs()
< 1e-5
);
let sp_ex = Epoch::from_gregorian_utc_hms(1996, 2, 7, 11, 22, 33);
let expected_et_s = -123_035_784.815_060_48;
assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6);
assert!(
(sp_ex.as_tai_seconds() - Epoch::from_et_seconds(expected_et_s).as_tai_seconds()).abs()
< 1e-5
);
let sp_ex = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
let expected_et_s = 476580220.1849411;
assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-6);
assert!((sp_ex.as_jde_utc_days() - 2457060.9739931).abs() < 1e-7);
let sp_ex = Epoch::from_et_seconds(66_312_032.184_939_09);
assert!(dbg!(2452312.500372511 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON);
assert!(dbg!(2452312.500372511 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10);
let sp_ex = Epoch::from_et_seconds(381_885_753.003_859_5);
assert!(dbg!(2455964.9739931 - sp_ex.as_jde_tdb_days()).abs() < 4.7e-10);
assert!((2455964.9739931 - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON);
let sp_ex = Epoch::from_et_seconds(0.0);
assert!(sp_ex.as_et_seconds() < std::f64::EPSILON);
assert!(dbg!(J2000_NAIF - sp_ex.as_jde_et_days()).abs() < std::f64::EPSILON);
assert!(dbg!(J2000_NAIF - sp_ex.as_jde_tdb_days()).abs() < 1e-7);
}
#[test]
fn test_from_str() {
use std::f64::EPSILON;
use std::str::FromStr;
let dt = Epoch::from_gregorian_utc(2017, 1, 14, 0, 31, 55, 0);
assert_eq!(dt, Epoch::from_str("2017-01-14T00:31:55 UTC").unwrap());
assert_eq!(dt, Epoch::from_str("2017-01-14T00:31:55").unwrap());
assert_eq!(dt, Epoch::from_str("2017-01-14 00:31:55").unwrap());
assert!(Epoch::from_str("2017-01-14 00:31:55 TAI").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 TT").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 ET").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 TDB").is_ok());
let jde = 2_452_312.500_372_511;
let as_tdb = Epoch::from_str("JD 2452312.500372511 TDB").unwrap();
let as_et = Epoch::from_str("JD 2452312.500372511 ET").unwrap();
let as_tai = Epoch::from_str("JD 2452312.500372511 TAI").unwrap();
assert!((as_tdb.as_jde_tdb_days() - jde).abs() < EPSILON);
assert!((as_et.as_jde_et_days() - jde).abs() < EPSILON);
assert!((as_tai.as_jde_tai_days() - jde).abs() < EPSILON);
assert!(
(Epoch::from_str("MJD 51544.5 TAI")
.unwrap()
.as_mjd_tai_days()
- 51544.5)
.abs()
< EPSILON
);
assert!((Epoch::from_str("SEC 0.5 TAI").unwrap().as_tai_seconds() - 0.5).abs() < EPSILON);
assert!(
dbg!(
Epoch::from_str("SEC 66312032.18493909 TDB")
.unwrap()
.as_tdb_seconds()
- 66312032.18493909
)
.abs()
< 1e-4
);
let greg = "2020-01-31T00:00:00 UTC";
assert_eq!(greg, Epoch::from_str(greg).unwrap().as_gregorian_utc_str());
let greg = "2020-01-31T00:00:00 TAI";
assert_eq!(greg, Epoch::from_str(greg).unwrap().as_gregorian_tai_str());
let greg = "2020-01-31T00:00:00 TDB";
assert_eq!(
"2020-01-30T23:59:59.999961853 TDB",
Epoch::from_str(greg)
.unwrap()
.as_gregorian_str(TimeSystem::TDB)
);
}
#[test]
fn ops() {
let sp_ex: Epoch =
Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33) + TimeUnit::Second * 1.0;
let expected_et_s = 381_885_819.184_935_87;
assert!(dbg!(sp_ex.as_tdb_seconds() - expected_et_s - 1.0).abs() < 1e-5);
let sp_ex: Epoch = sp_ex - TimeUnit::Second * 1.0;
assert!((sp_ex.as_tdb_seconds() - expected_et_s).abs() < 1e-5);
}
fn quorem(numerator: f64, denominator: f64) -> (i32, f64) {
if denominator == 0.0 {
panic!("cannot divide by zero");
}
let quotient = (numerator / denominator).floor() as i32;
let remainder = numerator % denominator;
if remainder >= 0.0 {
(quotient, remainder)
} else {
(quotient - 1, remainder + denominator)
}
}
#[test]
fn quorem_nominal_test() {
assert_eq!(quorem(24.0, 6.0), (4, 0.0));
assert_eq!(quorem(25.0, 6.0), (4, 1.0));
assert_eq!(quorem(6.0, 6.0), (1, 0.0));
assert_eq!(quorem(5.0, 6.0), (0, 5.0));
assert_eq!(quorem(3540.0, 3600.0), (0, 3540.0));
assert_eq!(quorem(3540.0, 60.0), (59, 0.0));
assert_eq!(quorem(24.0, -6.0), (-4, 0.0));
assert_eq!(quorem(-24.0, 6.0), (-4, 0.0));
assert_eq!(quorem(-24.0, -6.0), (4, 0.0));
}
#[test]
#[should_panic]
fn quorem_nil_den_test() {
assert_eq!(quorem(24.0, 0.0), (4, 0.0));
}
#[test]
fn test_range() {
let start = Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33);
let middle = Epoch::from_gregorian_utc_hms(2012, 2, 30, 0, 11, 22);
let end = Epoch::from_gregorian_utc_hms(2012, 3, 7, 11, 22, 33);
let rng = start..end;
assert_eq!(rng, std::ops::Range { start, end });
assert!(rng.contains(&middle));
}
#[test]
fn deser_test() {
use self::serde_derive::Deserialize;
#[derive(Deserialize)]
struct D {
pub e: Epoch,
}
}
#[test]
fn regression_test_gh_85() {
let earlier_epoch =
Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 100, TimeSystem::TAI).unwrap();
let later_epoch =
Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 200, TimeSystem::TAI).unwrap();
assert!(
later_epoch > earlier_epoch,
"later_epoch should be 100ns after earlier_epoch"
);
}