diff --git a/src/asn1der.rs b/src/asn1der.rs index e873ef3e..0e3e7731 100644 --- a/src/asn1der.rs +++ b/src/asn1der.rs @@ -62,8 +62,8 @@ impl<'a> Decode<'a> for Epoch { TimeScale::ET => Self::from_et_duration(duration), TimeScale::TDB => Self::from_tdb_duration(duration), TimeScale::UTC => Self::from_utc_duration(duration), - // GPST and QZSST share the same properties - TimeScale::GPST | TimeScale::QZSST => Self::from_gpst_duration(duration), + TimeScale::GPST => Self::from_gpst_duration(duration), + TimeScale::QZSST => Self::from_qzsst_duration(duration), TimeScale::GST => Self::from_gst_duration(duration), TimeScale::BDT => Self::from_bdt_duration(duration), }) @@ -93,7 +93,7 @@ impl<'a> Decode<'a> for Unit { // Testing the encoding and decoding of an Epoch inherently also tests the encoding and decoding of a Duration #[test] fn test_encdec() { - for ts_u8 in 0..=7 { + for ts_u8 in 0..=8 { let ts: TimeScale = ts_u8.into(); let epoch = if ts == TimeScale::UTC { @@ -108,10 +108,10 @@ fn test_encdec() { TimeScale::TT => epoch.to_tt_duration(), TimeScale::TDB => epoch.to_tdb_duration(), TimeScale::UTC => epoch.to_utc_duration(), + TimeScale::GPST => epoch.to_gpst_duration(), TimeScale::GST => epoch.to_gst_duration(), TimeScale::BDT => epoch.to_bdt_duration(), - // GPST and QZSST share the same properties - TimeScale::GPST | TimeScale::QZSST => epoch.to_gpst_duration(), + TimeScale::QZSST => epoch.to_qzsst_duration(), }; let e_dur = epoch.to_duration(); @@ -132,7 +132,7 @@ fn test_encdec() { // Check that the time scale used is preserved assert_eq!( encdec_epoch.time_scale, ts, - "Decoded time system incorrect {ts:?}" + "Decoded time system incorrect {ts:?}", ); } diff --git a/src/epoch.rs b/src/epoch.rs index d18c5453..20aa3fee 100644 --- a/src/epoch.rs +++ b/src/epoch.rs @@ -272,8 +272,8 @@ impl Epoch { TimeScale::ET => Self::from_et_duration(new_duration), TimeScale::TDB => Self::from_tdb_duration(new_duration), TimeScale::UTC => Self::from_utc_duration(new_duration), - // GPST and QZSST share the same properties - TimeScale::GPST | TimeScale::QZSST => Self::from_gpst_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), } @@ -347,6 +347,15 @@ impl Epoch { me } + #[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 + } + #[must_use] /// Initialize an Epoch from the provided duration since August 21st 1999 midnight pub fn from_gst_duration(duration: Duration) -> Self { @@ -391,6 +400,10 @@ impl Epoch { Self::from_mjd_in_time_scale(days, TimeScale::GPST) } #[must_use] + pub fn from_mjd_qzsst(days: f64) -> Self { + Self::from_mjd_in_time_scale(days, TimeScale::QZSST) + } + #[must_use] pub fn from_mjd_gst(days: f64) -> Self { Self::from_mjd_in_time_scale(days, TimeScale::GST) } @@ -427,6 +440,10 @@ impl Epoch { Self::from_jde_in_time_scale(days, TimeScale::GPST) } #[must_use] + pub fn from_jde_qzsst(days: f64) -> Self { + Self::from_jde_in_time_scale(days, TimeScale::QZSST) + } + #[must_use] pub fn from_jde_gst(days: f64) -> Self { Self::from_jde_in_time_scale(days, TimeScale::GST) } @@ -573,6 +590,34 @@ impl Epoch { ) } + #[must_use] + /// Initialize an Epoch from the number of seconds since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + pub fn from_qzsst_seconds(seconds: f64) -> Self { + Self::from_duration(Duration::from_f64(seconds, Unit::Second), TimeScale::QZSST) + } + + #[must_use] + /// Initialize an Epoch from the number of days since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + pub fn from_qzsst_days(days: f64) -> Self { + Self::from_duration(Duration::from_f64(days, Unit::Day), TimeScale::QZSST) + } + + #[must_use] + /// Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + /// This may be useful for time keeping devices that use QZSS as a time source. + pub fn from_qzsst_nanoseconds(nanoseconds: u64) -> Self { + Self::from_duration( + Duration { + centuries: 0, + nanoseconds, + }, + TimeScale::QZSST, + ) + } + #[must_use] /// Initialize an Epoch from the number of seconds since the GST Time Epoch, /// starting August 21st 1999 midnight (UTC) @@ -729,10 +774,13 @@ impl Epoch { 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), - // GPST and QZSST share the same properties - TimeScale::GPST | TimeScale::QZSST => { + 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()) } @@ -1426,6 +1474,31 @@ impl Epoch { Self::from_gpst_nanoseconds(nanoseconds) } + #[cfg(feature = "python")] + #[classmethod] + /// Initialize an Epoch from the number of seconds since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + fn init_from_qzsst_seconds(_cls: &PyType, seconds: f64) -> Self { + Self::from_qzsst_seconds(seconds) + } + + #[cfg(feature = "python")] + #[classmethod] + /// Initialize an Epoch from the number of days since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + fn init_from_qzsst_days(_cls: &PyType, days: f64) -> Self { + Self::from_qzsst_days(days) + } + + #[cfg(feature = "python")] + #[classmethod] + /// Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch, + /// defined as UTC midnight of January 5th to 6th 1980 (cf. ). + /// This may be useful for time keeping devices that use QZSS as a time source. + fn init_from_qzsst_nanoseconds(_cls: &PyType, nanoseconds: u64) -> Self { + Self::from_qzsst_nanoseconds(nanoseconds) + } + #[cfg(feature = "python")] #[classmethod] /// Initialize an Epoch from the number of seconds since the Galileo Time Epoch, @@ -1987,6 +2060,31 @@ impl Epoch { self.to_gpst_duration().to_unit(Unit::Day) } + #[must_use] + /// Returns seconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). + pub fn to_qzsst_seconds(&self) -> f64 { + self.to_qzsst_duration().to_seconds() + } + + #[must_use] + /// Returns `Duration` past QZSS time Epoch. + pub fn to_qzsst_duration(&self) -> Duration { + // GPST and QZSST share the same reference epoch + self.duration_since_j1900_tai - GPST_REF_EPOCH.to_tai_duration() + } + + /// Returns nanoseconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). + /// NOTE: This function will return an error if the centuries past QZSST time are not zero. + pub fn to_qzsst_nanoseconds(&self) -> Result { + self.to_nanoseconds_in_time_scale(TimeScale::QZSST) + } + + #[must_use] + /// Returns days past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). + pub fn to_qzsst_days(&self) -> f64 { + self.to_gpst_duration().to_unit(Unit::Day) + } + #[must_use] /// Returns seconds past GST (Galileo) Time Epoch pub fn to_gst_seconds(&self) -> f64 { @@ -1999,6 +2097,13 @@ impl Epoch { self.duration_since_j1900_tai - GST_REF_EPOCH.to_tai_duration() } + /// Returns nanoseconds past GST (Galileo) Time Epoch, starting on August 21st 1999 Midnight UT + /// (cf. ). + /// NOTE: This function will return an error if the centuries past GST time are not zero. + pub fn to_gst_nanoseconds(&self) -> Result { + self.to_nanoseconds_in_time_scale(TimeScale::GST) + } + #[must_use] /// Returns days past GST (Galileo) Time Epoch, /// starting on August 21st 1999 Midnight UT @@ -2007,13 +2112,6 @@ impl Epoch { self.to_gst_duration().to_unit(Unit::Day) } - /// Returns nanoseconds past GST (Galileo) Time Epoch, starting on August 21st 1999 Midnight UT - /// (cf. ). - /// NOTE: This function will return an error if the centuries past GST time are not zero. - pub fn to_gst_nanoseconds(&self) -> Result { - self.to_nanoseconds_in_time_scale(TimeScale::GST) - } - #[must_use] /// Returns seconds past BDT (BeiDou) Time Epoch pub fn to_bdt_seconds(&self) -> f64 { diff --git a/tests/epoch.rs b/tests/epoch.rs index 4becd71c..b6ce55ae 100644 --- a/tests/epoch.rs +++ b/tests/epoch.rs @@ -335,12 +335,31 @@ fn gpst() { let ref_gps = Epoch::from_gregorian_utc_at_midnight(1980, 01, 06); // Test 1sec into GPS timescale - let gnss = Epoch::from_gpst_seconds(1.0); - assert_eq!(gnss, ref_gps + 1.0 * Unit::Second); + let gps_1sec = Epoch::from_gpst_seconds(1.0); + assert_eq!(gps_1sec, ref_gps + 1.0 * Unit::Second); + + // 1sec into QZSS time scale returns the same date + let qzss_1sec = Epoch::from_qzsst_seconds(1.0); + assert_eq!(gps_1sec.to_utc_duration(), qzss_1sec.to_utc_duration()); + + // GPS and QZSS share the same properties at all times + assert_eq!(gps_1sec.to_gpst_seconds(), qzss_1sec.to_qzsst_seconds()); + assert_eq!( + gps_1sec.to_gpst_nanoseconds(), + qzss_1sec.to_qzsst_nanoseconds() + ); // Test 1+1/2 day into GPS timescale - let gnss = Epoch::from_gpst_days(1.5); - assert_eq!(gnss, ref_gps + 1.5 * Unit::Day); + let gps = Epoch::from_gpst_days(1.5); + assert_eq!(gps, ref_gps + 1.5 * Unit::Day); + + // 1sec into QZSS time scale returns the same date + let qzss = Epoch::from_qzsst_days(1.5); + assert_eq!(gps.to_utc_duration(), qzss.to_utc_duration()); + + // GPS and QZSS share the same properties at all times + assert_eq!(gps.to_gpst_seconds(), qzss.to_qzsst_seconds()); + assert_eq!(gps.to_gpst_nanoseconds(), qzss.to_qzsst_nanoseconds()); let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9); assert_eq!( @@ -390,6 +409,28 @@ fn gpst() { let epoch = Epoch::from_gregorian_utc_at_midnight(1980, 1, 1); assert!((epoch.to_gpst_seconds() + 5.0 * SECONDS_PER_DAY).abs() < EPSILON); assert!((epoch.to_gpst_days() + 5.0).abs() < EPSILON); + + // test other GPS / QZSS equalities + assert_eq!( + Epoch::from_mjd_gpst(0.77).to_utc_duration(), + Epoch::from_mjd_qzsst(0.77).to_utc_duration() + ); + assert_eq!( + Epoch::from_jde_gpst(1.23).to_utc_duration(), + Epoch::from_jde_qzsst(1.23).to_utc_duration() + ); + assert_eq!( + Epoch::from_qzsst_seconds(1024.768).to_utc_duration(), + Epoch::from_gpst_seconds(1024.768).to_utc_duration() + ); + assert_eq!( + Epoch::from_qzsst_days(987.654).to_utc_duration(), + Epoch::from_gpst_days(987.654).to_utc_duration() + ); + assert_eq!( + Epoch::from_qzsst_nanoseconds(543210987).to_utc_duration(), + Epoch::from_gpst_nanoseconds(543210987).to_utc_duration() + ); } #[test] @@ -1551,6 +1592,13 @@ fn test_time_of_week() { epoch_utc ); + // GPST and QZSST share the same properties at all times + 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(); + 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) let epoch = Epoch::from_time_of_week(0, 3_618_000_000_000, TimeScale::GPST); assert_eq!(epoch.to_gregorian_utc(), (1980, 01, 06, 01, 00, 0 + 18, 00));