From b3a70df8c07dbf015375507bea1b882987b31c50 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Tue, 18 Jun 2024 22:20:25 -0600 Subject: [PATCH] Add `to_time_scale_with_leap_seconds` which converts between time scales given a specific leap second provider (passed as reference) Also reproduce #255 bug with hifitime v4 --- src/epoch/mod.rs | 30 ++++++++++++-- tests/epoch.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/epoch/mod.rs b/src/epoch/mod.rs index e0c8777..37fb3bf 100644 --- a/src/epoch/mod.rs +++ b/src/epoch/mod.rs @@ -113,11 +113,15 @@ impl<'de> Deserialize<'de> for Epoch { // Defines the methods that should be classmethods in Python, but must be redefined as per https://github.com/PyO3/pyo3/issues/1003#issuecomment-844433346 impl Epoch { #[must_use] - /// Converts self to another time scale + /// Converts self to another time scale, using the leap second provider for any conversion involving leap seconds /// /// As per the [Rust naming convention](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv), /// this borrows an Epoch and returns an owned Epoch. - pub fn to_time_scale(&self, ts: TimeScale) -> Self { + pub fn to_time_scale_with_leap_seconds( + &self, + ts: TimeScale, + provider: &L, + ) -> Self { if ts == self.time_scale { // Do nothing, just return a copy *self @@ -159,7 +163,11 @@ impl Epoch { // Assume this is TAI let mut tai_assumption = *self; tai_assumption.time_scale = TimeScale::TAI; - self.duration + tai_assumption.leap_seconds(true).unwrap_or(0.0).seconds() + self.duration + + tai_assumption + .leap_seconds_with(true, provider) + .unwrap_or(0.0) + .seconds() } TimeScale::GPST => self.duration + GPST_REF_EPOCH.to_tai_duration(), TimeScale::GST => self.duration + GST_REF_EPOCH.to_tai_duration(), @@ -221,7 +229,11 @@ impl Epoch { time_scale: TimeScale::TAI, }; // TAI = UTC + leap_seconds <=> UTC = TAI - leap_seconds - prime_epoch_offset - epoch.leap_seconds(true).unwrap_or(0.0).seconds() + prime_epoch_offset + - epoch + .leap_seconds_with(true, provider) + .unwrap_or(0.0) + .seconds() } TimeScale::GPST => prime_epoch_offset - GPST_REF_EPOCH.to_tai_duration(), TimeScale::GST => prime_epoch_offset - GST_REF_EPOCH.to_tai_duration(), @@ -236,6 +248,16 @@ impl Epoch { } } + #[must_use] + /// Converts self to another time scale + /// + /// Note that this is a shortcut to to_time_scale_with_leap_seconds with the LatestLeapSeconds. + /// As per the [Rust naming convention](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv), + /// this borrows an Epoch and returns an owned Epoch. + pub fn to_time_scale(&self, ts: TimeScale) -> Self { + self.to_time_scale_with_leap_seconds(ts, &LatestLeapSeconds::default()) + } + #[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 { diff --git a/tests/epoch.rs b/tests/epoch.rs index 89701dc..9fc18eb 100644 --- a/tests/epoch.rs +++ b/tests/epoch.rs @@ -2102,3 +2102,103 @@ fn regression_test_gh_288() { format!("{}", epoch.to_isoformat()) ); } + +#[cfg(feature = "std")] +#[test] +fn regression_test_gh_255() { + use hifitime::leap_seconds::{LeapSecond, LeapSecondProvider}; + + #[derive(Clone)] + struct LeapTable { + table: Vec, + } + + impl LeapSecondProvider for LeapTable { + fn entries(&self) -> &[LeapSecond] { + &self.table + } + } + + let leap_insert = LeapTable { + table: vec![ + LeapSecond::new(50.0, 3.0, true), + LeapSecond::new(100.0, 4.0, true), + ], + }; + let leap_delete = LeapTable { + table: vec![ + LeapSecond::new(50.0, 3.0, true), + LeapSecond::new(100.0, 2.0, true), + ], + }; + + println!(); + println!("Insert leap second @ 100, from +3 to +4"); + println!("UTC -> TAI -> UTC"); + for i in 96..=102 { + let time = Epoch::from_utc_seconds(i as f64); + let tai = time.to_time_scale_with_leap_seconds(TimeScale::TAI, &leap_insert); + let tai_s = tai.duration.to_seconds().round() as i64; + let utc = tai.to_time_scale_with_leap_seconds(TimeScale::UTC, &leap_insert); + let utc_s = utc.duration.to_seconds().round() as i64; + println!( + "{:3} -> {:3} -> {:3}, delta = {}", + i, + tai_s, + utc_s, + utc_s - i + ); + if i == 99 { + let time = Epoch::from_tai_seconds(103.0); + let utc = time.to_time_scale_with_leap_seconds(TimeScale::UTC, &leap_insert); + let utc_s = utc.duration.to_seconds().round() as i64; + println!(" -> {:3} -> {:3}, delta = {}", 103, utc_s, 0); + } + } + + println!(); + println!("Delete leap second @ 100, from +3 to +2"); + println!("UTC -> TAI -> UTC"); + for i in 96..=102 { + let time = Epoch::from_utc_seconds(i as f64); + let tai = time.to_time_scale_with_leap_seconds(TimeScale::TAI, &leap_delete); + let tai_s = tai.duration.to_seconds().round() as i64; + let utc = tai.to_time_scale_with_leap_seconds(TimeScale::UTC, &leap_delete); + let utc_s = utc.duration.to_seconds().round() as i64; + println!( + "{:3} -> {:3} -> {:3}, delta = {}", + i, + tai_s, + utc_s, + utc_s - i + ); + } + + // println!(); + // println!("=== Proposed fix ==="); + + // println!(); + // println!("Insert leap second @ 100, from +3 to +4"); + // println!("UTC -> TAI -> UTC"); + // for i in 96..=102 { + // let time = from_utc_seconds(i as f64, leap_insert.clone()); + // let tai = time.to_tai_seconds().round() as i64; + // let utc = fixed_to_utc(&time, leap_insert.clone()).round() as i64; + // println!("{:3} -> {:3} -> {:3}, delta = {}", i, tai, utc, utc - i); + // if i == 99 { + // let time = Epoch::from_tai_seconds(103.0); + // let utc = to_utc_seconds(&time, leap_insert.clone()).round() as i64; + // println!(" -> {:3} -> {:3}, delta = {}", 103, utc, 0); + // } + // } + + // println!(); + // println!("Delete leap second @ 100, from +3 to +2"); + // println!("UTC -> TAI -> UTC"); + // for i in 96..=102 { + // let time = from_utc_seconds(i as f64, leap_delete.clone()); + // let tai = time.to_tai_seconds().round() as i64; + // let utc = fixed_to_utc(&time, leap_delete.clone()).round() as i64; + // println!("{:3} -> {:3} -> {:3}, delta = {}", i, tai, utc, utc - i); + // } +}