Skip to content

Commit

Permalink
initiate the timescale trait
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume W. Bres <guillaume.bressaix@gmail.com>
  • Loading branch information
gwbres committed May 27, 2023
1 parent 0dc5fac commit 463b14f
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 95 deletions.
169 changes: 87 additions & 82 deletions src/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,22 +263,78 @@ impl Epoch {
None
}

/// Makes a copy of self and sets the duration and time scale appropriately given the new duration
#[must_use]
pub fn from_duration(new_duration: Duration, time_scale: TimeScale) -> Self {
match time_scale {
TimeScale::TAI => Self::from_tai_duration(new_duration),
TimeScale::TT => Self::from_tt_duration(new_duration),
TimeScale::ET => Self::from_et_duration(new_duration),
TimeScale::TDB => Self::from_tdb_duration(new_duration),
TimeScale::UTC => Self::from_utc_duration(new_duration),
TimeScale::GPST => Self::from_gpst_duration(new_duration),
TimeScale::QZSST => Self::from_qzsst_duration(new_duration),
TimeScale::GST => Self::from_gst_duration(new_duration),
TimeScale::BDT => Self::from_bdt_duration(new_duration),
/// Creates an epoch from given duration expressed in given timescale.
/// In case of ET, TDB Timescales, a duration since J2000 is expected.
#[must_use]
pub fn from_duration(new_duration: Duration, ts: TimeScale) -> Self {
match ts {
TimeScale::TAI => Self {
duration_since_j1900_tai: new_duration,
time_scale: TimeScale::TAI,
},
TimeScale::ET => {
// Run a Newton Raphston to convert find the correct value of the
let mut seconds_j2000 = new_duration.to_seconds();
for _ in 0..5 {
seconds_j2000 += -NAIF_K
* (NAIF_M0
+ NAIF_M1 * seconds_j2000
+ NAIF_EB * (NAIF_M0 + NAIF_M1 * seconds_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_j2000 - (TT_OFFSET_MS * Unit::Millisecond).to_seconds(),
);
// Match SPICE by changing the UTC definition.
Self {
duration_since_j1900_tai: (new_duration.to_seconds() - delta_et_tai)
* Unit::Second
+ J2000_TO_J1900_DURATION,
time_scale: TimeScale::ET,
}
}
TimeScale::TDB => {
let gamma = Self::inner_g(new_duration.to_seconds());
let delta_tdb_tai = gamma * Unit::Second + TT_OFFSET_MS * Unit::Millisecond;
// Offset back to J1900.
Self {
duration_since_j1900_tai: new_duration - delta_tdb_tai
+ J2000_TO_J1900_DURATION,
time_scale: TimeScale::TDB,
}
}
ts => {
let mut tai_epoch = ts.tai_reference_epoch();
tai_epoch += new_duration;
match ts {
TimeScale::TT => {
tai_epoch -= TT_OFFSET_MS * Unit::Millisecond;
}
TimeScale::ET => {}
_ => {}
}
// leap second management
if ts.uses_leap_seconds() {
tai_epoch += tai_epoch.leap_seconds(true).unwrap_or(0.0) * Unit::Second;
}
tai_epoch.with_timescale(ts)
}
}
}

/// Assigns given TimeScale to self.
/// This is not a conversion operation, but
/// simply a definition.
/// To convert into a timescale,
/// you should use .into(TimeScale)
pub fn with_timescale(&self, ts: TimeScale) -> Self {
let mut s = self.clone();
s.time_scale = ts;
s
}

#[must_use]
/// Creates a new Epoch from a Duration as the time difference between this epoch and TAI reference epoch.
pub const fn from_tai_duration(duration: Duration) -> Self {
Expand Down Expand Up @@ -317,14 +373,7 @@ impl Epoch {
#[must_use]
/// Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight
pub fn from_utc_duration(duration: Duration) -> Self {
let mut e = Self::from_tai_duration(duration);
// 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
e.duration_since_j1900_tai += e.leap_seconds(true).unwrap_or(0.0) * Unit::Second;
e.time_scale = TimeScale::UTC;
e
Self::from_duration(duration, TimeScale::UTC)
}

#[must_use]
Expand All @@ -342,34 +391,25 @@ impl Epoch {
#[must_use]
/// Initialize an Epoch from the provided duration since 1980 January 6 at midnight
pub fn from_gpst_duration(duration: Duration) -> Self {
let mut me = Self::from_tai_duration(GPST_REF_EPOCH.to_tai_duration() + duration);
me.time_scale = TimeScale::GPST;
me
Self::from_duration(duration, TimeScale::GPST)
}

#[must_use]
/// Initialize an Epoch from the provided duration since 1980 January 6 at midnight
pub fn from_qzsst_duration(duration: Duration) -> Self {
// QZSST and GPST share the same reference epoch
let mut me = Self::from_tai_duration(GPST_REF_EPOCH.to_tai_duration() + duration);
me.time_scale = TimeScale::QZSST;
me
Self::from_duration(duration, TimeScale::QZSST)
}

#[must_use]
/// Initialize an Epoch from the provided duration since August 21st 1999 midnight
pub fn from_gst_duration(duration: Duration) -> Self {
let mut me = Self::from_tai_duration(GST_REF_EPOCH.to_tai_duration() + duration);
me.time_scale = TimeScale::GST;
me
Self::from_duration(duration, TimeScale::GST)
}

#[must_use]
/// Initialize an Epoch from the provided duration since January 1st midnight
pub fn from_bdt_duration(duration: Duration) -> Self {
let mut me = Self::from_tai_duration(BDT_REF_EPOCH.to_tai_duration() + duration);
me.time_scale = TimeScale::BDT;
me
Self::from_duration(duration, TimeScale::BDT)
}

#[must_use]
Expand Down Expand Up @@ -465,10 +505,7 @@ impl Epoch {
#[must_use]
/// Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)
pub fn from_tt_duration(duration: Duration) -> Self {
Self {
duration_since_j1900_tai: duration - Unit::Millisecond * TT_OFFSET_MS,
time_scale: TimeScale::TT,
}
Self::from_duration(duration, TimeScale::TT)
}

#[must_use]
Expand All @@ -491,16 +528,16 @@ impl Epoch {
/// In order to match SPICE, the as_et_duration() function will manually get rid of that difference.
#[must_use]
pub fn from_et_duration(duration_since_j2000: Duration) -> Self {
Self::from_duration(duration_since_j2000, TimeScale::ET)
/*
// Run a Newton Raphston to convert find the correct value of the
let mut seconds_j2000 = duration_since_j2000.to_seconds();
for _ in 0..5 {
seconds_j2000 += -NAIF_K
* (NAIF_M0
+ NAIF_M1 * seconds_j2000
+ NAIF_EB * (NAIF_M0 + NAIF_M1 * seconds_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 =
Expand All @@ -512,7 +549,7 @@ impl Epoch {
* Unit::Second
+ J2000_TO_J1900_DURATION,
time_scale: TimeScale::ET,
}
}*/
}

#[must_use]
Expand All @@ -530,16 +567,7 @@ impl Epoch {
#[must_use]
/// Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI.
pub fn from_tdb_duration(duration_since_j2000: Duration) -> Epoch {
let gamma = Self::inner_g(duration_since_j2000.to_seconds());

let delta_tdb_tai = gamma * Unit::Second + TT_OFFSET_MS * Unit::Millisecond;

// Offset back to J1900.
Self {
duration_since_j1900_tai: duration_since_j2000 - delta_tdb_tai
+ J2000_TO_J1900_DURATION,
time_scale: TimeScale::TDB,
}
Self::from_duration(duration_since_j2000, TimeScale::TDB)
}

#[must_use]
Expand Down Expand Up @@ -581,13 +609,7 @@ impl Epoch {
/// defined as UTC midnight of January 5th to 6th 1980 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS#GPS_Time_.28GPST.29>).
/// This may be useful for time keeping devices that use GPS as a time source.
pub fn from_gpst_nanoseconds(nanoseconds: u64) -> Self {
Self::from_duration(
Duration {
centuries: 0,
nanoseconds,
},
TimeScale::GPST,
)
Self::from_duration(nanoseconds as f64 * Unit::Nanosecond, TimeScale::GPST)
}

#[must_use]
Expand Down Expand Up @@ -667,13 +689,7 @@ impl Epoch {
/// starting on January 1st 2006 (cf. <https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS>).
/// This may be useful for time keeping devices that use BDT as a time source.
pub fn from_bdt_nanoseconds(nanoseconds: u64) -> Self {
Self::from_duration(
Duration {
centuries: 0,
nanoseconds,
},
TimeScale::BDT,
)
Self::from_duration(nanoseconds as f64 * Unit::Nanosecond, TimeScale::BDT)
}

#[must_use]
Expand Down Expand Up @@ -769,24 +785,12 @@ impl Epoch {

// NOTE: For ET and TDB, we make sure to offset the duration back to J2000 since those functions expect a J2000 input.
Ok(match time_scale {
TimeScale::TAI => Self::from_tai_duration(duration_wrt_1900),
TimeScale::TT => Self::from_tt_duration(duration_wrt_1900),
TimeScale::ET => Self::from_et_duration(duration_wrt_1900 - J2000_TO_J1900_DURATION),
TimeScale::TDB => Self::from_tdb_duration(duration_wrt_1900 - J2000_TO_J1900_DURATION),
TimeScale::UTC => Self::from_utc_duration(duration_wrt_1900),
TimeScale::GPST => {
Self::from_gpst_duration(duration_wrt_1900 - GPST_REF_EPOCH.to_tai_duration())
}
// QZSS and GPST share the same reference epoch
TimeScale::QZSST => {
Self::from_qzsst_duration(duration_wrt_1900 - GPST_REF_EPOCH.to_tai_duration())
}
TimeScale::GST => {
Self::from_gst_duration(duration_wrt_1900 - GST_REF_EPOCH.to_tai_duration())
}
TimeScale::BDT => {
Self::from_bdt_duration(duration_wrt_1900 - BDT_REF_EPOCH.to_tai_duration())
}
ts => Self::from_duration(
duration_wrt_1900 - ts.tai_reference_epoch().to_tai_duration(),
ts,
),
})
}

Expand Down Expand Up @@ -3344,7 +3348,7 @@ fn formal_epoch_reciprocity_tdb() {
let duration = Duration::from_parts(19510, 3155759999999997938);

// TDB
let ts_offset = TimeScale::TDB.ref_epoch() - TimeScale::TAI.ref_epoch();
let ts_offset = TimeScale::TDB.tai_reference_epoch() - TimeScale::TAI.tai_reference_epoch();
if duration > Duration::MIN + ts_offset && duration < Duration::MAX - ts_offset {
// We guard TDB from durations that are would hit the MIN or the MAX.
// TDB is centered on J2000 but the Epoch is on J1900. So on initialization, we offset by one century and twelve hours.
Expand All @@ -3367,6 +3371,7 @@ fn formal_epoch_reciprocity_tdb() {

#[cfg(kani)]
#[kani::proof]
#[test]
fn formal_epoch_reciprocity_gpst() {
let duration: Duration = kani::any();

Expand Down
11 changes: 7 additions & 4 deletions src/timescale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub const SECONDS_GPS_TAI_OFFSET: f64 = 2_524_953_619.0;
pub const SECONDS_GPS_TAI_OFFSET_I64: i64 = 2_524_953_619;
pub const DAYS_GPS_TAI_OFFSET: f64 = SECONDS_GPS_TAI_OFFSET / SECONDS_PER_DAY;

/// QZSS and GPS share the same reference epoch
pub const QZSST_REF_EPOCH: Epoch = GPST_REF_EPOCH;

/// GST (Galileo) reference epoch is 13 seconds before 1999 August 21 UTC at midnight.
pub const GST_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
centuries: 0,
Expand Down Expand Up @@ -126,18 +129,18 @@ impl TimeScale {
matches!(self, Self::GPST | Self::GST | Self::BDT | Self::QZSST)
}

/// Returns Reference Epoch (t(0)) for given timescale
pub const fn ref_epoch(&self) -> Epoch {
/// Returns Reference Epoch (Time Scale initialization date),
/// for given timescale, in TAI.
pub const fn tai_reference_epoch(&self) -> Epoch {
match self {
Self::GPST => GPST_REF_EPOCH,
Self::GST => GST_REF_EPOCH,
Self::BDT => BDT_REF_EPOCH,
Self::ET => J2000_REF_EPOCH_ET,
Self::TDB => J2000_REF_EPOCH_TDB,
Self::QZSST => QZSST_REF_EPOCH,
// Explicit on purpose in case more time scales end up being supported.
Self::TT | Self::TAI | Self::UTC => J1900_REF_EPOCH,
// QZSS time shares the same starting point as GPST
Self::QZSST => GPST_REF_EPOCH,
}
}
}
Expand Down
21 changes: 12 additions & 9 deletions tests/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,9 +1243,9 @@ fn test_timescale_recip() {
assert_eq!(utc_epoch, utc_epoch.set(utc_epoch.to_duration()));
// 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::TAI,
TimeScale::ET,
TimeScale::TDB,
//TimeScale::TDB,
TimeScale::TT,
TimeScale::UTC,
] {
Expand Down Expand Up @@ -1596,7 +1596,8 @@ fn test_time_of_week() {
let epoch_qzsst = epoch.in_time_scale(TimeScale::QZSST);
assert_eq!(epoch.to_gregorian_utc(), epoch_qzsst.to_gregorian_utc());

let gps_qzss_offset = TimeScale::GPST.ref_epoch() - TimeScale::QZSST.ref_epoch();
let gps_qzss_offset =
TimeScale::GPST.tai_reference_epoch() - TimeScale::QZSST.tai_reference_epoch();
assert_eq!(gps_qzss_offset.total_nanoseconds(), 0); // no offset

// 06/01/1980 01:00:00 = 1H into GPST <=> (0, 3_618_000_000_000)
Expand Down Expand Up @@ -1670,7 +1671,7 @@ fn test_time_of_week() {

// 1H into Galileo timescale
let epoch = Epoch::from_time_of_week(0, 3_600_000_000_000, TimeScale::GST);
let expected_tai = TimeScale::GST.ref_epoch() + Duration::from_hours(1.0);
let expected_tai = TimeScale::GST.tai_reference_epoch() + Duration::from_hours(1.0);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(epoch.to_time_of_week(), (0, 3_600_000_000_000));

Expand All @@ -1684,8 +1685,9 @@ fn test_time_of_week() {

// 1W + 128H into Galileo timescale
let epoch = Epoch::from_time_of_week(1, 128 * 3600 * 1_000_000_000, TimeScale::GST);
let expected_tai =
TimeScale::GST.ref_epoch() + Duration::from_days(7.0) + Duration::from_hours(128.0);
let expected_tai = TimeScale::GST.tai_reference_epoch()
+ Duration::from_days(7.0)
+ Duration::from_hours(128.0);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(epoch.to_time_of_week(), (1, 128 * 3600 * 1_000_000_000));

Expand All @@ -1703,7 +1705,7 @@ fn test_time_of_week() {
13 * 3600 * 1_000_000_000 + 1800 * 1_000_000_000,
TimeScale::BDT,
);
let expected_tai = TimeScale::BDT.ref_epoch() + Duration::from_hours(13.5);
let expected_tai = TimeScale::BDT.tai_reference_epoch() + Duration::from_hours(13.5);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(
epoch.to_time_of_week(),
Expand All @@ -1724,8 +1726,9 @@ fn test_time_of_week() {
36 * 3600 * 1_000_000_000 + 900 * 1_000_000_000,
TimeScale::BDT,
);
let expected_tai =
TimeScale::BDT.ref_epoch() + Duration::from_days(70.0) + Duration::from_hours(36.25);
let expected_tai = TimeScale::BDT.tai_reference_epoch()
+ Duration::from_days(70.0)
+ Duration::from_hours(36.25);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(
epoch.to_time_of_week(),
Expand Down

0 comments on commit 463b14f

Please sign in to comment.