Skip to content

Commit

Permalink
fix eq and comparison issue: compare in same time scale
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume W. Bres <[email protected]>
  • Loading branch information
gwbres committed May 28, 2023
1 parent 5de034b commit 9d0afbd
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/efmt/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ impl fmt::Display for Formatter {

if self.format.need_gregorian() {
// This is a specific branch so we don't recompute the gregorian information for each token.
let (y, mm, dd, hh, min, s, nanos) = Epoch::compute_gregorian(self.epoch.to_duration());
let (y, mm, dd, hh, min, s, nanos) =
Epoch::compute_gregorian(self.epoch.to_duration_in_time_scale(TimeScale::TAI));
// And format.
for (i, maybe_item) in self
.format
Expand Down
68 changes: 41 additions & 27 deletions src/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::efmt::format::Format;

use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use core::fmt;
use core::hash::{Hash, Hasher};
use core::hash::Hash;
use core::ops::{Add, AddAssign, Sub, SubAssign};

use crate::ParsingErrors;
Expand Down Expand Up @@ -123,7 +123,7 @@ const CUMULATIVE_DAYS_FOR_MONTH: [u16; 12] = {
/// Defines a nanosecond-precision Epoch.
///
/// Refer to the appropriate functions for initializing this Epoch from different time scales or representations.
#[derive(Copy, Clone, Eq, Default)]
#[derive(Copy, Clone, Default, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -234,12 +234,6 @@ impl PartialEq for Epoch {
}
}

impl Hash for Epoch {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.duration.hash(hasher);
}
}

impl PartialOrd for Epoch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(
Expand Down Expand Up @@ -297,11 +291,41 @@ impl Epoch {
pub fn to_time_scale(&self, ts: TimeScale) -> Self {
match ts {
TimeScale::TAI => {
// conversion to TAI: remove time scale reference point
let mut new_epoch = self.clone();
// if previous time scale supported leap seconds: remove them
if self.time_scale.uses_leap_seconds() {
new_epoch -= new_epoch.leap_seconds(true).unwrap_or(0.0) * Unit::Second;
}
// conversion to TAI: remove time scale reference point
new_epoch.duration -= ts.tai_reference_epoch().duration;
new_epoch.with_time_scale(TimeScale::TAI)
}
TimeScale::ET => {
// first convert back to TAI
let mut tai_epoch = self.to_time_scale(TimeScale::TAI);
// seconds past J2000
//TODO: this operation is not feasible is Self is not PAST J2000
let duration_since_j2000 = tai_epoch - J2000_REF_EPOCH;
let mut seconds_since_j2000 = duration_since_j2000.to_seconds();
for _ in 0..5 {
seconds_since_j2000 += -NAIF_K
* (NAIF_M0
+ NAIF_M1 * seconds_since_j2000
+ NAIF_EB * (NAIF_M0 + NAIF_M1 * seconds_since_j2000).sin())
.sin();
}
// At this point, we have a good estimate of the number of seconds
// of this epoch. Reverse the algorithm:
let delta_et_tai = Self::delta_et_tai(
seconds_since_j2000 - (TT_OFFSET_MS * Unit::Millisecond).to_seconds(),
);
// Match the SPICE by changing the UTC definition
Self {
duration: (tai_epoch.duration.to_seconds() - delta_et_tai) * Unit::Second
+ J2000_TO_J1900_DURATION,
time_scale: TimeScale::ET,
}
}
TimeScale::TDB => {
// first convert back to TAI
let mut tai_epoch = self.to_time_scale(TimeScale::TAI);
Expand All @@ -312,15 +336,15 @@ impl Epoch {
let gamma = Self::inner_g(seconds_since_j2000);
let delta_tdb_tai = gamma * Unit::Second + TT_OFFSET_MS * Unit::Millisecond;
tai_epoch += delta_tdb_tai;
tai_epoch -= J2000_TO_J1900_DURATION; // TDB time scale is expressed past J2000
tai_epoch += J2000_TO_J1900_DURATION; // TDB time scale is expressed past J2000
tai_epoch.with_time_scale(TimeScale::TDB)
}
ts => {
// first convert back to TAI
let mut tai_epoch = self.to_time_scale(TimeScale::TAI);
// leap second management
if ts.uses_leap_seconds() {
// TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds
// new time scale supports leap seconds: add them
tai_epoch += tai_epoch.leap_seconds(true).unwrap_or(0.0) * Unit::Second;
}
// time scale specificities
Expand Down Expand Up @@ -775,7 +799,7 @@ impl Epoch {

Ok(Self {
duration: duration_wrt_1900,
time_scale: TimeScale::TAI,
time_scale: TimeScale::UTC,
}
.to_time_scale(time_scale))
}
Expand Down Expand Up @@ -834,15 +858,8 @@ impl Epoch {
second: u8,
nanos: u32,
) -> Result<Self, Errors> {
let mut if_tai =
Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)?;
// Compute the TAI to UTC offset at this time.
// We have the time in TAI. But we were given UTC.
// Hence, we need to _add_ the leap seconds to get the actual TAI time.
// TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds
if_tai.duration += if_tai.leap_seconds(true).unwrap_or(0.0) * Unit::Second;
if_tai.time_scale = TimeScale::UTC;
Ok(if_tai)
let if_tai = Self::maybe_from_gregorian_tai(year, month, day, hour, minute, second, nanos)?;
Ok(if_tai.to_time_scale(TimeScale::UTC))
}

#[must_use]
Expand Down Expand Up @@ -3310,12 +3327,9 @@ fn formal_epoch_reciprocity_tdb() {
let epoch: Epoch = Epoch::from_duration(duration, time_scale);
let out_duration = epoch.to_duration_in_time_scale(time_scale);
assert_eq!(out_duration.centuries, duration.centuries);
if out_duration.nanoseconds > duration.nanoseconds {
assert!(out_duration.nanoseconds - duration.nanoseconds < 500_000);
} else if out_duration.nanoseconds < duration.nanoseconds {
assert!(duration.nanoseconds - out_duration.nanoseconds < 500_000);
}
// Else: they match and we're happy.
assert_eq!(out_duration.nanoseconds, duration.nanoseconds);
let error = (out_duration.nanoseconds - duration.nanoseconds) as f64;
assert!(error.abs() < 500_000.0, "error: {}", error);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/timescale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub const UNIX_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {

/// Enum of the different time systems available
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeScale {
Expand Down
15 changes: 11 additions & 4 deletions src/timeseries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ mod tests {
use crate::{Epoch, TimeSeries, Unit};

#[test]
fn test_timeseries() {
fn test_exclusive_timeseries() {
let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14);
let end = Epoch::from_gregorian_utc_at_noon(2017, 1, 14);
let step = Unit::Hour * 2;
Expand All @@ -319,13 +319,20 @@ mod tests {
assert_ne!(epoch, end, "Ending epoch of exclusive time series is wrong");
}
#[cfg(feature = "std")]
println!("{}", epoch);
println!("tests::exclusive_timeseries::{}", epoch);
count += 1;
}

assert_eq!(count, 6, "Should have five items in this iterator");
}

count = 0;
#[test]
fn test_inclusive_timeseries() {
let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14);
let end = Epoch::from_gregorian_utc_at_noon(2017, 1, 14);
let step = Unit::Hour * 2;

let mut count = 0;
let time_series = TimeSeries::inclusive(start, end, step);
for epoch in time_series {
if count == 0 {
Expand All @@ -337,7 +344,7 @@ mod tests {
assert_eq!(epoch, end, "Ending epoch of inclusive time series is wrong");
}
#[cfg(feature = "std")]
println!("{}", epoch);
println!("tests::inclusive_timeseries::{}", epoch);
count += 1;
}

Expand Down
19 changes: 16 additions & 3 deletions tests/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ fn test_const_ops() {
assert!((j2000_offset.to_unit(Unit::Day) - J2000_OFFSET).abs() < f64::EPSILON);
}

#[test]
fn test_from_gregorian() {
let utc_epoch = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14);
let tai_epoch = Epoch::from_gregorian_at_midnight(2017, 1, 14, TimeScale::TAI);
assert_eq!(utc_epoch.to_time_scale(TimeScale::TAI), tai_epoch);
assert_eq!(utc_epoch, tai_epoch.to_time_scale(TimeScale::UTC));

let utc_epoch = Epoch::from_gregorian_utc(2016, 12, 31, 23, 59, 59, 0);
let tai_epoch = Epoch::from_gregorian(2016, 12, 31, 23, 59, 59, 0, TimeScale::TAI);
assert_eq!(utc_epoch.to_time_scale(TimeScale::TAI), tai_epoch);
assert_eq!(utc_epoch, tai_epoch.to_time_scale(TimeScale::UTC));
}

#[allow(clippy::float_equality_without_abs)]
#[test]
fn utc_epochs() {
Expand Down Expand Up @@ -1242,9 +1255,9 @@ fn test_timescale_recip() {
let recip_func = |utc_epoch: Epoch| {
// Test that we can convert this epoch into another time scale and re-initialize it correctly from that value.
for ts in &[
//TimeScale::TAI,
TimeScale::ET,
TimeScale::TDB,
TimeScale::TAI,
//TimeScale::ET,
//TimeScale::TDB,
TimeScale::TT,
TimeScale::UTC,
] {
Expand Down

0 comments on commit 9d0afbd

Please sign in to comment.