diff --git a/CHANGELOG.md b/CHANGELOG.md index 6578ca4..4300d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,41 @@ and this project adheres to [Semantic Versioning](http://semver.org/). # [unreleased] +Major API changes for the time API. If you are using the time API, it is strongly recommended +to check all the API changes in the **Changed** chapter. + +## Fixed + +- CUC timestamp was fixed to include leap second corrections because it is based on the TAI + time reference. The default CUC time object do not implement `CcsdsTimeProvider` anymore + because the trait methods require cached leap second information. This task is now performed + by the `cuc::CucTimeWithLeapSecs` which implements the trait. + ## Added - `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions. +- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib` + feature gate. +- Added `CcsdsTimeProvider::timelib_date_time`. + +## Changed + +- Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time` +- Renamed `CcsdsTimeCodes` to `CcsdsTimeCode` +- Renamed `cds::TimeProvider` to `cds::CdsTime` +- Renamed `cuc::TimeProviderCcsdsEpoch` to `cuc::CucTime` +- `UnixTimestamp` renamed to `UnixTime` +- `UnixTime` seconds are now private and can be retrieved using the `secs` member method. +- `UnixTime::new` renamed to `UnixTime::new_checked`. +- `UnixTime` now has a nanosecond subsecond precision. The `new` constructor now expects + nanoseconds as the second argument. +- Added new `UnixTime::new_subsec_millis` and `UnixTime::new_subsec_millis_checked` API + to still allow creating a timestamp with only millisecond subsecond resolution. +- `CcsdsTimeProvider` now has a new `subsec_nanos` method in addition to a default + implementation for the `subsec_millis` method. +- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`. +- Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`. +- Added `UnixTime::timelib_date_time`. # [v0.11.0-rc.0] 2024-03-04 diff --git a/Cargo.toml b/Cargo.toml index c1273d4..c0d6a8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,22 +34,31 @@ optional = true default-features = false features = ["derive"] +[dependencies.time] +version = "0.3" +default-features = false +optional = true + [dependencies.chrono] version = "0.4" default-features = false +optional = true [dependencies.num-traits] version = "0.2" default-features = false -[dev-dependencies.postcard] -version = "1" +[dev-dependencies] +postcard = "1" +chrono = "0.4" [features] default = ["std"] std = ["chrono/std", "chrono/clock", "alloc", "thiserror"] serde = ["dep:serde", "chrono/serde"] alloc = ["postcard/alloc", "chrono/alloc"] +chrono = ["dep:chrono"] +timelib = ["dep:time"] [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index e2551ca..c136b6a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ deserializing them with an appropriate `serde` provider like ## Optional Features - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s + - [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library. + - [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library. # Examples diff --git a/src/ecss/tm.rs b/src/ecss/tm.rs index fedfb0e..837a6dc 100644 --- a/src/ecss/tm.rs +++ b/src/ecss/tm.rs @@ -1097,7 +1097,7 @@ mod tests { use super::*; use crate::ecss::PusVersion::PusC; - use crate::time::cds::TimeProvider; + use crate::time::cds::CdsTime; #[cfg(feature = "serde")] use crate::time::CcsdsTimeProvider; use crate::SpHeader; @@ -1136,7 +1136,7 @@ mod tests { #[test] fn test_basic_simple_api() { let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); - let time_provider = TimeProvider::new_with_u16_days(0, 0); + let time_provider = CdsTime::new_with_u16_days(0, 0); let mut stamp_buf: [u8; 8] = [0; 8]; let pus_tm = PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) @@ -1534,7 +1534,7 @@ mod tests { #[cfg(feature = "serde")] fn test_serialization_creator_serde() { let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); - let time_provider = TimeProvider::new_with_u16_days(0, 0); + let time_provider = CdsTime::new_with_u16_days(0, 0); let mut stamp_buf: [u8; 8] = [0; 8]; let pus_tm = PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) @@ -1549,7 +1549,7 @@ mod tests { #[cfg(feature = "serde")] fn test_serialization_reader_serde() { let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); - let time_provider = TimeProvider::new_with_u16_days(0, 0); + let time_provider = CdsTime::new_with_u16_days(0, 0); let mut stamp_buf: [u8; 8] = [0; 8]; let pus_tm = PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) diff --git a/src/time/ascii.rs b/src/time/ascii.rs index ad0a210..c749c49 100644 --- a/src/time/ascii.rs +++ b/src/time/ascii.rs @@ -2,11 +2,8 @@ //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 . //! See [chrono::DateTime::format] for a usage example of the generated //! [chrono::format::DelayedFormat] structs. -#[cfg(feature = "alloc")] -use chrono::{ - format::{DelayedFormat, StrftimeItems}, - DateTime, Utc, -}; +#[cfg(all(feature = "alloc", feature = "chrono"))] +pub use alloc_mod_chrono::*; /// Tuple of format string and formatted size for time code A. /// @@ -34,36 +31,41 @@ pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21); /// Three digits are used for the decimal fraction and a terminator is added at the end. pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22); -/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub fn generate_time_code_a(date: &DateTime) -> DelayedFormat> { - date.format(FMT_STR_CODE_A_WITH_SIZE.0) -} +#[cfg(all(feature = "alloc", feature = "chrono"))] +pub mod alloc_mod_chrono { + use super::*; + use chrono::{ + format::{DelayedFormat, StrftimeItems}, + DateTime, Utc, + }; -/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub fn generate_time_code_a_terminated( - date: &DateTime, -) -> DelayedFormat> { - date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0) -} + /// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format. + #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))] + pub fn generate_time_code_a(date: &DateTime) -> DelayedFormat> { + date.format(FMT_STR_CODE_A_WITH_SIZE.0) + } -/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub fn generate_time_code_b(date: &DateTime) -> DelayedFormat> { - date.format(FMT_STR_CODE_B_WITH_SIZE.0) -} + /// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format. + #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))] + pub fn generate_time_code_a_terminated( + date: &DateTime, + ) -> DelayedFormat> { + date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0) + } -/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub fn generate_time_code_b_terminated( - date: &DateTime, -) -> DelayedFormat> { - date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0) + /// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format. + #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))] + pub fn generate_time_code_b(date: &DateTime) -> DelayedFormat> { + date.format(FMT_STR_CODE_B_WITH_SIZE.0) + } + + /// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format. + #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))] + pub fn generate_time_code_b_terminated( + date: &DateTime, + ) -> DelayedFormat> { + date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0) + } } #[cfg(test)] @@ -77,7 +79,7 @@ mod tests { let date = Utc::now(); let stamp_formatter = generate_time_code_a(&date); let stamp = format!("{}", stamp_formatter); - let t_sep = stamp.find("T"); + let t_sep = stamp.find('T'); assert!(t_sep.is_some()); assert_eq!(t_sep.unwrap(), 10); assert_eq!(stamp.len(), FMT_STR_CODE_A_WITH_SIZE.1); @@ -88,10 +90,10 @@ mod tests { let date = Utc::now(); let stamp_formatter = generate_time_code_a_terminated(&date); let stamp = format!("{}", stamp_formatter); - let t_sep = stamp.find("T"); + let t_sep = stamp.find('T'); assert!(t_sep.is_some()); assert_eq!(t_sep.unwrap(), 10); - let z_terminator = stamp.find("Z"); + let z_terminator = stamp.find('Z'); assert!(z_terminator.is_some()); assert_eq!( z_terminator.unwrap(), @@ -105,7 +107,7 @@ mod tests { let date = Utc::now(); let stamp_formatter = generate_time_code_b(&date); let stamp = format!("{}", stamp_formatter); - let t_sep = stamp.find("T"); + let t_sep = stamp.find('T'); assert!(t_sep.is_some()); assert_eq!(t_sep.unwrap(), 8); assert_eq!(stamp.len(), FMT_STR_CODE_B_WITH_SIZE.1); @@ -116,10 +118,10 @@ mod tests { let date = Utc::now(); let stamp_formatter = generate_time_code_b_terminated(&date); let stamp = format!("{}", stamp_formatter); - let t_sep = stamp.find("T"); + let t_sep = stamp.find('T'); assert!(t_sep.is_some()); assert_eq!(t_sep.unwrap(), 8); - let z_terminator = stamp.find("Z"); + let z_terminator = stamp.find('Z'); assert!(z_terminator.is_some()); assert_eq!( z_terminator.unwrap(), diff --git a/src/time/cds.rs b/src/time/cds.rs index 52bf7cf..227eab8 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -4,21 +4,42 @@ //! The core data structure to do this is the [TimeProvider] struct and the //! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the //! struct from a bytestream. -use super::*; use crate::private::Sealed; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; -use chrono::Datelike; -#[cfg(feature = "alloc")] -use core::any::Any; +use crate::ByteConversionError; use core::cmp::Ordering; -use core::fmt::Debug; +use core::fmt::{Debug, Display, Formatter}; use core::ops::{Add, AddAssign}; use core::time::Duration; + use delegate::delegate; +#[cfg(feature = "std")] +use super::StdTimestampError; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::time::{SystemTime, SystemTimeError}; + +#[cfg(feature = "chrono")] +use chrono::Datelike; + +#[cfg(feature = "alloc")] +use super::ccsds_time_code_from_p_field; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use core::any::Any; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::{ + ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCode, CcsdsTimeProvider, TimeReader, + TimeWriter, TimestampError, UnixTime, MS_PER_DAY, SECONDS_PER_DAY, +}; + /// Base value for the preamble field for a time field parser to determine the time field type. -pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::Cds as u8) << 4; +pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::Cds as u8) << 4; pub const MIN_CDS_FIELD_LEN: usize = 7; pub const MAX_DAYS_24_BITS: u32 = 2_u32.pow(24) - 1; @@ -154,21 +175,20 @@ pub fn precision_from_pfield(pfield: u8) -> SubmillisPrecision { /// } /// // It is possible to add a Duration offset to a timestamp provider. Add 5 minutes offset here /// let offset = Duration::from_secs(60 * 5); -/// let former_unix_seconds = timestamp_now.unix_seconds(); +/// let former_unix_seconds = timestamp_now.unix_secs(); /// let timestamp_in_5_minutes = timestamp_now + offset; -/// assert_eq!(timestamp_in_5_minutes.unix_seconds(), former_unix_seconds + 5 * 60); +/// assert_eq!(timestamp_in_5_minutes.unix_secs(), former_unix_seconds + 5 * 60); /// ``` #[derive(Debug, Copy, Clone, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TimeProvider { +pub struct CdsTime { pfield: u8, ccsds_days: DaysLen::FieldType, ms_of_day: u32, - // submillis_precision: SubmillisPrecision, submillis: u32, /// This is not strictly necessary but still cached because it significantly simplifies the /// calculation of [`DateTime`]. - unix_stamp: UnixTimestamp, + unix_stamp: UnixTime, } /// Common properties for all CDS time providers. @@ -194,36 +214,47 @@ trait CdsConverter: CdsCommon { struct ConversionFromUnix { ccsds_days: u32, ms_of_day: u32, + submilis_prec: SubmillisPrecision, + submillis: u32, /// This is a side-product of the calculation of the CCSDS days. It is useful for /// re-calculating the datetime at a later point and therefore supplied as well. unix_days_seconds: i64, } impl ConversionFromUnix { - fn new(unix_seconds: i64, subsec_millis: u32) -> Result { + fn new( + unix_seconds: i64, + subsec_nanos: u32, + precision: SubmillisPrecision, + ) -> Result { let (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds); let ccsds_days = unix_to_ccsds_days(unix_days); - if ccsds_days == 0 && (secs_of_day > 0 || subsec_millis > 0) || ccsds_days < 0 { - let millis = if unix_seconds < 0 { - unix_seconds * 1000 - subsec_millis as i64 - } else { - unix_seconds * 1000 + subsec_millis as i64 - }; + if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 { return Err(TimestampError::DateBeforeCcsdsEpoch( - Utc.timestamp_millis_opt(millis).unwrap(), + UnixTime::new_checked(unix_seconds, subsec_nanos) + .expect("unix timestamp creation failed"), )); } + let ms_of_day = secs_of_day * 1000 + subsec_nanos / 10_u32.pow(6); + + let submillis = match precision { + SubmillisPrecision::Microseconds => (subsec_nanos / 1_000) % 1000, + SubmillisPrecision::Picoseconds => (subsec_nanos % 10_u32.pow(6)) * 1000, + _ => 0, + }; Ok(Self { ccsds_days: unix_to_ccsds_days(unix_days) as u32, - ms_of_day: secs_of_day * 1000 + subsec_millis, + ms_of_day, unix_days_seconds: unix_days * SECONDS_PER_DAY as i64, + submilis_prec: precision, + submillis, }) } } impl CdsCommon for ConversionFromUnix { fn submillis_precision(&self) -> SubmillisPrecision { - SubmillisPrecision::Absent + self.submilis_prec } fn ms_of_day(&self) -> u32 { @@ -235,7 +266,7 @@ impl CdsCommon for ConversionFromUnix { } fn submillis(&self) -> u32 { - 0 + self.submillis } } @@ -245,13 +276,13 @@ impl CdsConverter for ConversionFromUnix { } } /// Helper struct which generates fields for the CDS time provider from a datetime. -struct ConversionFromDatetime { +struct ConversionFromChronoDatetime { unix_conversion: ConversionFromUnix, submillis_prec: SubmillisPrecision, submillis: u32, } -impl CdsCommon for ConversionFromDatetime { +impl CdsCommon for ConversionFromChronoDatetime { fn submillis_precision(&self) -> SubmillisPrecision { self.submillis_prec } @@ -268,7 +299,7 @@ impl CdsCommon for ConversionFromDatetime { } } -impl CdsConverter for ConversionFromDatetime { +impl CdsConverter for ConversionFromChronoDatetime { delegate! {to self.unix_conversion { fn unix_days_seconds(&self) -> i64; }} } @@ -285,27 +316,36 @@ fn calc_unix_days_and_secs_of_day(unix_seconds: i64) -> (i64, u32) { (unix_days, secs_of_day as u32) } -impl ConversionFromDatetime { - fn new(dt: &DateTime) -> Result { +#[cfg(feature = "chrono")] +impl ConversionFromChronoDatetime { + fn new(dt: &chrono::DateTime) -> Result { Self::new_generic(dt, SubmillisPrecision::Absent) } - fn new_with_submillis_us_prec(dt: &DateTime) -> Result { + fn new_with_submillis_us_prec( + dt: &chrono::DateTime, + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Microseconds) } - fn new_with_submillis_ps_prec(dt: &DateTime) -> Result { + fn new_with_submillis_ps_prec( + dt: &chrono::DateTime, + ) -> Result { Self::new_generic(dt, SubmillisPrecision::Picoseconds) } - fn new_generic(dt: &DateTime, prec: SubmillisPrecision) -> Result { + #[cfg(feature = "chrono")] + fn new_generic( + dt: &chrono::DateTime, + prec: SubmillisPrecision, + ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt))); } // The contained values in the conversion should be all positive now let unix_conversion = - ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis())?; + ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_nanos(), prec)?; let mut submillis = 0; match prec { SubmillisPrecision::Microseconds => { @@ -350,7 +390,8 @@ impl ConversionFromNow { let epoch = now.as_secs(); // This should always return a value with valid (non-negative) CCSDS days, // so it is okay to unwrap - let unix_conversion = ConversionFromUnix::new(epoch as i64, now.subsec_millis()).unwrap(); + let unix_conversion = + ConversionFromUnix::new(epoch as i64, now.subsec_nanos(), prec).unwrap(); let mut submillis = 0; match prec { @@ -397,10 +438,10 @@ impl CdsConverter for ConversionFromNow { pub trait DynCdsTimeProvider: CcsdsTimeProvider + CdsTimestamp + TimeWriter + Any {} #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -impl DynCdsTimeProvider for TimeProvider {} +impl DynCdsTimeProvider for CdsTime {} #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -impl DynCdsTimeProvider for TimeProvider {} +impl DynCdsTimeProvider for CdsTime {} /// This function returns the correct [TimeProvider] instance from a raw byte array /// by checking the length of days field. It also checks the CCSDS time code for correctness. @@ -436,25 +477,25 @@ pub fn get_dyn_time_provider_from_bytes( let time_code = ccsds_time_code_from_p_field(buf[0]); if let Err(e) = time_code { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::Cds, + expected: CcsdsTimeCode::Cds, found: e, }); } let time_code = time_code.unwrap(); - if time_code != CcsdsTimeCodes::Cds { + if time_code != CcsdsTimeCode::Cds { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::Cds, + expected: CcsdsTimeCode::Cds, found: time_code as u8, }); } if length_of_day_segment_from_pfield(buf[0]) == LengthOfDaySegment::Short16Bits { - Ok(Box::new(TimeProvider::from_bytes_with_u16_days(buf)?)) + Ok(Box::new(CdsTime::from_bytes_with_u16_days(buf)?)) } else { - Ok(Box::new(TimeProvider::from_bytes_with_u24_days(buf)?)) + Ok(Box::new(CdsTime::from_bytes_with_u24_days(buf)?)) } } -impl CdsCommon for TimeProvider { +impl CdsCommon for CdsTime { fn submillis_precision(&self) -> SubmillisPrecision { precision_from_pfield(self.pfield) } @@ -472,7 +513,7 @@ impl CdsCommon for TimeProvider TimeProvider { +impl CdsTime { /// Please note that a precision value of 0 will be converted to [None] (no precision). pub fn set_submillis(&mut self, prec: SubmillisPrecision, value: u32) -> bool { self.pfield &= !(0b11); @@ -531,19 +572,19 @@ impl TimeProvider { )); } let pfield = buf[0]; - match CcsdsTimeCodes::try_from(pfield >> 4 & 0b111) { + match CcsdsTimeCode::try_from(pfield >> 4 & 0b111) { Ok(cds_type) => match cds_type { - CcsdsTimeCodes::Cds => (), + CcsdsTimeCode::Cds => (), _ => { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::Cds, + expected: CcsdsTimeCode::Cds, found: cds_type as u8, }) } }, _ => { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::Cds, + expected: CcsdsTimeCode::Cds, found: pfield >> 4 & 0b111, }); } @@ -596,20 +637,11 @@ impl TimeProvider { } else { unix_days_seconds += seconds_of_day; } - self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16); - } - - fn calc_date_time(&self, ns_since_last_second: u32) -> Option> { - assert!( - ns_since_last_second < 10_u32.pow(9), - "Invalid MS since last second" - ); - if let LocalResult::Single(val) = - Utc.timestamp_opt(self.unix_stamp.unix_seconds, ns_since_last_second) - { - return Some(val); + let mut subsec_nanos = (ms_of_day % 1000) * 10_u32.pow(6); + if let Some(precision) = self.precision_as_ns() { + subsec_nanos += precision; } - None + self.unix_stamp = UnixTime::new(unix_days_seconds, subsec_nanos); } fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { @@ -644,36 +676,40 @@ impl TimeProvider { Ok(provider) } + #[cfg(feature = "chrono")] fn from_dt_generic( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } + #[cfg(feature = "chrono")] fn from_dt_generic_us_prec( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_us_prec(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_us_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } + #[cfg(feature = "chrono")] fn from_dt_generic_ps_prec( - dt: &DateTime, + dt: &chrono::DateTime, days_len: LengthOfDaySegment, ) -> Result { - let conv_from_dt = ConversionFromDatetime::new_with_submillis_ps_prec(dt)?; + let conv_from_dt = ConversionFromChronoDatetime::new_with_submillis_ps_prec(dt)?; Self::generic_from_conversion(days_len, conv_from_dt) } fn from_unix_generic( - unix_stamp: &UnixTimestamp, + unix_stamp: &UnixTime, days_len: LengthOfDaySegment, + submillis_prec: SubmillisPrecision, ) -> Result { let conv_from_dt = - ConversionFromUnix::new(unix_stamp.unix_seconds, unix_stamp.subsecond_millis as u32)?; + ConversionFromUnix::new(unix_stamp.secs, unix_stamp.subsec_nanos, submillis_prec)?; Self::generic_from_conversion(days_len, conv_from_dt) } @@ -765,7 +801,7 @@ impl TimeProvider { } } -impl TimeProvider { +impl CdsTime { /// Generate a new timestamp provider with the days field width set to 24 bits pub fn new_with_u24_days(ccsds_days: u32, ms_of_day: u32) -> Result { if ccsds_days > MAX_DAYS_24_BITS { @@ -788,7 +824,10 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_dt_with_u24_days(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits) } @@ -799,19 +838,26 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_unix_secs_with_u24_days( - unix_stamp: &UnixTimestamp, + pub fn from_unix_stamp_with_u24_days( + unix_stamp: &UnixTime, + submillis_prec: SubmillisPrecision, ) -> Result { - Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec) } /// Like [Self::from_dt_with_u24_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u24_days_us_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days_us_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits) } /// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u24_days_ps_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u24_days_ps_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits) } @@ -856,7 +902,7 @@ impl TimeProvider { } } -impl TimeProvider { +impl CdsTime { /// Generate a new timestamp provider with the days field width set to 16 bits pub fn new_with_u16_days(ccsds_days: u16, ms_of_day: u32) -> Self { // This should never fail, type system ensures CCSDS can not be negative or too large @@ -868,7 +914,10 @@ impl TimeProvider { /// This function will return a [TimestampError::DateBeforeCcsdsEpoch] or a /// [TimestampError::Cds] if the time is before the CCSDS epoch (01-01-1958 00:00:00) or /// the CCSDS days value exceeds the allowed bit width (16 bits). - pub fn from_dt_with_u16_days(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits) } @@ -886,19 +935,26 @@ impl TimeProvider { /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// or the CCSDS days value exceeds the allowed bit width (24 bits). - pub fn from_unix_secs_with_u16_days( - unix_stamp: &UnixTimestamp, + pub fn from_unix_stamp_with_u16_days( + unix_stamp: &UnixTime, + submillis_prec: SubmillisPrecision, ) -> Result { - Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits) + Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec) } /// Like [Self::from_dt_with_u16_days] but with microsecond sub-millisecond precision. - pub fn from_dt_with_u16_days_us_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days_us_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits) } /// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision. - pub fn from_dt_with_u16_days_ps_precision(dt: &DateTime) -> Result { + #[cfg(feature = "chrono")] + pub fn from_dt_with_u16_days_ps_precision( + dt: &chrono::DateTime, + ) -> Result { Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits) } @@ -944,7 +1000,7 @@ impl TimeProvider { } fn add_for_max_ccsds_days_val( - time_provider: &TimeProvider, + time_provider: &CdsTime, max_days_val: u32, duration: Duration, ) -> (u32, u32, u32) { @@ -1018,13 +1074,13 @@ fn add_for_max_ccsds_days_val( (next_ccsds_days, next_ms_of_day, submillis) } -impl CdsTimestamp for TimeProvider { +impl CdsTimestamp for CdsTime { fn len_of_day_seg(&self) -> LengthOfDaySegment { LengthOfDaySegment::Short16Bits } } -impl CdsTimestamp for TimeProvider { +impl CdsTimestamp for CdsTime { fn len_of_day_seg(&self) -> LengthOfDaySegment { LengthOfDaySegment::Long24Bits } @@ -1033,7 +1089,7 @@ impl CdsTimestamp for TimeProvider { /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. -impl Add for TimeProvider { +impl Add for CdsTime { type Output = Self; fn add(self, duration: Duration) -> Self::Output { @@ -1045,8 +1101,8 @@ impl Add for TimeProvider { } } -impl Add for &TimeProvider { - type Output = TimeProvider; +impl Add for &CdsTime { + type Output = CdsTime; fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = @@ -1060,7 +1116,7 @@ impl Add for &TimeProvider { /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. -impl Add for TimeProvider { +impl Add for CdsTime { type Output = Self; fn add(self, duration: Duration) -> Self::Output { @@ -1072,8 +1128,8 @@ impl Add for TimeProvider { } } -impl Add for &TimeProvider { - type Output = TimeProvider; +impl Add for &CdsTime { + type Output = CdsTime; fn add(self, duration: Duration) -> Self::Output { let (next_ccsds_days, next_ms_of_day, precision) = add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); @@ -1087,7 +1143,7 @@ impl Add for &TimeProvider { /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. -impl AddAssign for TimeProvider { +impl AddAssign for CdsTime { fn add_assign(&mut self, duration: Duration) { let (next_ccsds_days, next_ms_of_day, submillis) = add_for_max_ccsds_days_val(self, u16::MAX as u32, duration); @@ -1100,7 +1156,7 @@ impl AddAssign for TimeProvider { /// Allows adding an duration in form of an offset. Please note that the CCSDS days will rollover /// when they overflow, because addition needs to be infallible. The user needs to check for a /// days overflow when this is a possibility and might be a problem. -impl AddAssign for TimeProvider { +impl AddAssign for CdsTime { fn add_assign(&mut self, duration: Duration) { let (next_ccsds_days, next_ms_of_day, submillis) = add_for_max_ccsds_days_val(self, MAX_DAYS_24_BITS, duration); @@ -1110,25 +1166,27 @@ impl AddAssign for TimeProvider { } } -impl TryFrom> for TimeProvider { +#[cfg(feature = "chrono")] +impl TryFrom> for CdsTime { type Error = TimestampError; - fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(&dt)?; + fn try_from(dt: chrono::DateTime) -> Result { + let conversion = ConversionFromChronoDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) } } -impl TryFrom> for TimeProvider { +#[cfg(feature = "chrono")] +impl TryFrom> for CdsTime { type Error = TimestampError; - fn try_from(dt: DateTime) -> Result { - let conversion = ConversionFromDatetime::new(&dt)?; + fn try_from(dt: chrono::DateTime) -> Result { + let conversion = ConversionFromChronoDatetime::new(&dt)?; Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) } } -impl CcsdsTimeProvider for TimeProvider { +impl CcsdsTimeProvider for CdsTime { fn len_as_bytes(&self) -> usize { Self::calc_stamp_len(self.pfield) } @@ -1137,52 +1195,38 @@ impl CcsdsTimeProvider for TimeProvider CcsdsTimeCodes { - CcsdsTimeCodes::Cds + fn ccdsd_time_code(&self) -> CcsdsTimeCode { + CcsdsTimeCode::Cds } #[inline] - fn unix_seconds(&self) -> i64 { - self.unix_stamp.unix_seconds + fn unix_secs(&self) -> i64 { + self.unix_stamp.secs } #[inline] - fn subsecond_millis(&self) -> u16 { - self.unix_stamp.subsecond_millis + fn subsec_nanos(&self) -> u32 { + self.unix_stamp.subsec_nanos } #[inline] - fn unix_stamp(&self) -> UnixTimestamp { + fn unix_stamp(&self) -> UnixTime { self.unix_stamp } - - fn date_time(&self) -> Option> { - let mut ns_since_last_sec = (self.ms_of_day % 1000) * 10_u32.pow(6); - match self.submillis_precision() { - SubmillisPrecision::Microseconds => { - ns_since_last_sec += self.submillis() * 1000; - } - SubmillisPrecision::Picoseconds => { - ns_since_last_sec += self.submillis() / 1000; - } - _ => (), - } - self.calc_date_time(ns_since_last_sec) - } } -impl TimeReader for TimeProvider { +impl TimeReader for CdsTime { fn from_bytes(buf: &[u8]) -> Result { Self::from_bytes_with_u16_days(buf) } } -impl TimeReader for TimeProvider { +impl TimeReader for CdsTime { fn from_bytes(buf: &[u8]) -> Result { Self::from_bytes_with_u24_days(buf) } } -impl TimeWriter for TimeProvider { +impl TimeWriter for CdsTime { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { self.length_check(buf, self.len_as_bytes())?; buf[0] = self.pfield; @@ -1205,7 +1249,7 @@ impl TimeWriter for TimeProvider { } } -impl TimeWriter for TimeProvider { +impl TimeWriter for CdsTime { fn write_to_bytes(&self, buf: &mut [u8]) -> Result { self.length_check(buf, self.len_as_bytes())?; buf[0] = self.pfield; @@ -1229,7 +1273,7 @@ impl TimeWriter for TimeProvider { } } -impl PartialEq for TimeProvider { +impl PartialEq for CdsTime { fn eq(&self, other: &Self) -> bool { if self.ccsds_days == other.ccsds_days && self.ms_of_day == other.ms_of_day @@ -1241,7 +1285,7 @@ impl PartialEq for TimeProvider PartialOrd for TimeProvider { +impl PartialOrd for CdsTime { fn partial_cmp(&self, other: &Self) -> Option { if self == other { return Some(Ordering::Equal); @@ -1269,14 +1313,14 @@ impl PartialOrd for TimeProvider Ord for TimeProvider { +impl Ord for CdsTime { fn cmp(&self, other: &Self) -> Ordering { PartialOrd::partial_cmp(self, other).unwrap() } } -impl From> for TimeProvider { - fn from(value: TimeProvider) -> Self { +impl From> for CdsTime { + fn from(value: CdsTime) -> Self { // This function only fails if the days value exceeds 24 bits, which is not possible here, // so it is okay to unwrap. Self::new_with_u24_days(value.ccsds_days_as_u32(), value.ms_of_day()).unwrap() @@ -1284,9 +1328,9 @@ impl From> for TimeProvider { } /// This conversion can fail if the days value exceeds 16 bits. -impl TryFrom> for TimeProvider { +impl TryFrom> for CdsTime { type Error = CdsError; - fn try_from(value: TimeProvider) -> Result { + fn try_from(value: CdsTime) -> Result { let ccsds_days = value.ccsds_days_as_u32(); if ccsds_days > u16::MAX as u32 { return Err(CdsError::InvalidCcsdsDays(ccsds_days as i64)); @@ -1302,6 +1346,7 @@ impl TryFrom> for TimeProvider { mod tests { use super::*; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; + use crate::time::{UnixTime, DAYS_CCSDS_TO_UNIX, MS_PER_DAY}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; use alloc::string::ToString; use chrono::{Datelike, NaiveDate, Timelike}; @@ -1311,25 +1356,25 @@ mod tests { #[test] fn test_time_stamp_zero_args() { - let time_stamper = TimeProvider::new_with_u16_days(0, 0); + let time_stamper = CdsTime::new_with_u16_days(0, 0); let unix_stamp = time_stamper.unix_stamp(); assert_eq!( - unix_stamp.unix_seconds, + unix_stamp.secs, (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 ); - let subsecond_millis = unix_stamp.subsecond_millis; + let subsecond_millis = unix_stamp.subsec_nanos; assert_eq!(subsecond_millis, 0); assert_eq!( time_stamper.submillis_precision(), SubmillisPrecision::Absent ); - assert_eq!(time_stamper.subsecond_millis(), 0); - assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); + assert_eq!(time_stamper.subsec_nanos(), 0); + assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCode::Cds); assert_eq!( time_stamper.p_field(), - (1, [(CcsdsTimeCodes::Cds as u8) << 4, 0]) + (1, [(CcsdsTimeCode::Cds as u8) << 4, 0]) ); - let date_time = time_stamper.date_time().unwrap(); + let date_time = time_stamper.chrono_date_time().unwrap(); assert_eq!(date_time.year(), 1958); assert_eq!(date_time.month(), 1); assert_eq!(date_time.day(), 1); @@ -1340,28 +1385,30 @@ mod tests { #[test] fn test_time_stamp_unix_epoch() { - let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0); - assert_eq!(time_stamper.unix_stamp().unix_seconds, 0); + let time_stamper = CdsTime::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 0); + assert_eq!(time_stamper.unix_stamp().secs, 0); assert_eq!( time_stamper.submillis_precision(), SubmillisPrecision::Absent ); - let date_time = time_stamper.date_time().unwrap(); + let date_time = time_stamper.chrono_date_time().unwrap(); assert_eq!(date_time.year(), 1970); assert_eq!(date_time.month(), 1); assert_eq!(date_time.day(), 1); assert_eq!(date_time.hour(), 0); assert_eq!(date_time.minute(), 0); assert_eq!(date_time.second(), 0); - let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40); - assert_eq!(time_stamper.subsecond_millis(), 40); - let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); - assert_eq!(time_stamper.subsecond_millis(), 40); + let time_stamper = CdsTime::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40); + assert_eq!(time_stamper.subsec_nanos(), 40 * 10_u32.pow(6)); + assert_eq!(time_stamper.subsec_millis(), 40); + let time_stamper = CdsTime::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); + assert_eq!(time_stamper.subsec_nanos(), 40 * 10_u32.pow(6)); + assert_eq!(time_stamper.subsec_millis(), 40); } #[test] fn test_large_days_field_write() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0x10203040); + let time_stamper = CdsTime::new_with_u24_days(0x108020_u32, 0x10203040); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); assert_eq!(time_stamper.len_as_bytes(), 8); @@ -1380,13 +1427,13 @@ mod tests { #[test] fn test_large_days_field_read() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020_u32, 0); + let time_stamper = CdsTime::new_with_u24_days(0x108020_u32, 0); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); let mut buf = [0; 16]; let written = time_stamper.write_to_bytes(&mut buf); assert!(written.is_ok()); - let provider = TimeProvider::::from_bytes(&buf); + let provider = CdsTime::::from_bytes(&buf); assert!(provider.is_ok()); let provider = provider.unwrap(); assert_eq!(provider.ccsds_days(), 0x108020); @@ -1395,13 +1442,13 @@ mod tests { #[test] fn test_large_days_field_read_invalid_ctor() { - let time_stamper = TimeProvider::new_with_u24_days(0x108020, 0); + let time_stamper = CdsTime::new_with_u24_days(0x108020, 0); assert!(time_stamper.is_ok()); let time_stamper = time_stamper.unwrap(); let mut buf = [0; 16]; let written = time_stamper.write_to_bytes(&mut buf); assert!(written.is_ok()); - let faulty_ctor = TimeProvider::::from_bytes(&buf); + let faulty_ctor = CdsTime::::from_bytes(&buf); assert!(faulty_ctor.is_err()); let error = faulty_ctor.unwrap_err(); if let TimestampError::Cds(CdsError::InvalidCtorForDaysOfLenInPreamble(len_of_day)) = error @@ -1415,15 +1462,15 @@ mod tests { #[test] fn test_write() { let mut buf = [0; 16]; - let time_stamper_0 = TimeProvider::new_with_u16_days(0, 0); + let time_stamper_0 = CdsTime::new_with_u16_days(0, 0); let unix_stamp = time_stamper_0.unix_stamp(); assert_eq!( - unix_stamp.unix_seconds, + unix_stamp.secs, (DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32).into() ); let mut res = time_stamper_0.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4); + assert_eq!(buf[0], (CcsdsTimeCode::Cds as u8) << 4); assert_eq!( u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")), 0 @@ -1432,10 +1479,10 @@ mod tests { u32::from_be_bytes(buf[3..7].try_into().expect("Byte conversion failed")), 0 ); - let time_stamper_1 = TimeProvider::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); + let time_stamper_1 = CdsTime::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); res = time_stamper_1.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4); + assert_eq!(buf[0], (CcsdsTimeCode::Cds as u8) << 4); assert_eq!( u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")), u16::MAX - 1 @@ -1449,7 +1496,7 @@ mod tests { #[test] fn test_faulty_write_buf_too_small() { let mut buf = [0; 7]; - let time_stamper = TimeProvider::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); + let time_stamper = CdsTime::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); for i in 0..6 { let res = time_stamper.write_to_bytes(&mut buf[0..i]); assert!(res.is_err()); @@ -1475,7 +1522,7 @@ mod tests { fn test_faulty_read_buf_too_small() { let buf = [0; 7]; for i in 0..6 { - let res = TimeProvider::::from_bytes(&buf[0..i]); + let res = CdsTime::::from_bytes(&buf[0..i]); assert!(res.is_err()); let err = res.unwrap_err(); match err { @@ -1496,15 +1543,15 @@ mod tests { #[test] fn test_faulty_invalid_pfield() { let mut buf = [0; 16]; - let time_stamper_0 = TimeProvider::new_with_u16_days(0, 0); + let time_stamper_0 = CdsTime::new_with_u16_days(0, 0); let res = time_stamper_0.write_to_bytes(&mut buf); assert!(res.is_ok()); buf[0] = 0; - let res = TimeProvider::::from_bytes(&buf); + let res = CdsTime::::from_bytes(&buf); assert!(res.is_err()); let err = res.unwrap_err(); if let InvalidTimeCode { expected, found } = err { - assert_eq!(expected, CcsdsTimeCodes::Cds); + assert_eq!(expected, CcsdsTimeCode::Cds); assert_eq!(found, 0); assert_eq!( err.to_string(), @@ -1516,10 +1563,10 @@ mod tests { #[test] fn test_reading() { let mut buf = [0; 16]; - let time_stamper = TimeProvider::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); + let time_stamper = CdsTime::new_with_u16_days(u16::MAX - 1, u32::MAX - 1); let res = time_stamper.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(buf[0], (CcsdsTimeCodes::Cds as u8) << 4); + assert_eq!(buf[0], (CcsdsTimeCode::Cds as u8) << 4); assert_eq!( u16::from_be_bytes(buf[1..3].try_into().expect("Byte conversion failed")), u16::MAX - 1 @@ -1529,17 +1576,17 @@ mod tests { u32::MAX - 1 ); - let read_stamp: TimeProvider = - TimeProvider::from_bytes(&buf).expect("Reading timestamp failed"); + let read_stamp: CdsTime = + CdsTime::from_bytes(&buf).expect("Reading timestamp failed"); assert_eq!(read_stamp.ccsds_days(), u16::MAX - 1); assert_eq!(read_stamp.ms_of_day(), u32::MAX - 1); } fn generic_now_test( - timestamp_now: TimeProvider, - compare_stamp: DateTime, + timestamp_now: CdsTime, + compare_stamp: chrono::DateTime, ) { - let dt = timestamp_now.date_time().unwrap(); + let dt = timestamp_now.chrono_date_time().unwrap(); if compare_stamp.year() > dt.year() { assert_eq!(compare_stamp.year() - dt.year(), 1); } else { @@ -1562,42 +1609,42 @@ mod tests { #[test] fn test_time_now() { - let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); - let compare_stamp = Utc::now(); + let timestamp_now = CdsTime::from_now_with_u16_days().unwrap(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_us_prec() { - let timestamp_now = TimeProvider::from_now_with_u16_days_us_precision().unwrap(); - let compare_stamp = Utc::now(); + let timestamp_now = CdsTime::from_now_with_u16_days_us_precision().unwrap(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec() { - let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let timestamp_now = CdsTime::from_now_with_u16_days_ps_precision().unwrap(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec_u16_days() { - let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let timestamp_now = CdsTime::from_now_with_u16_days_ps_precision().unwrap(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_time_now_ps_prec_u24_days() { - let timestamp_now = TimeProvider::from_now_with_u24_days_ps_precision().unwrap(); - let compare_stamp = Utc::now(); + let timestamp_now = CdsTime::from_now_with_u24_days_ps_precision().unwrap(); + let compare_stamp = chrono::Utc::now(); generic_now_test(timestamp_now, compare_stamp); } #[test] fn test_submillis_precision_micros() { - let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); + let mut time_stamper = CdsTime::new_with_u16_days(0, 0); time_stamper.set_submillis(SubmillisPrecision::Microseconds, 500); assert_eq!( time_stamper.submillis_precision(), @@ -1615,7 +1662,7 @@ mod tests { #[test] fn test_submillis_precision_picos() { - let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); + let mut time_stamper = CdsTime::new_with_u16_days(0, 0); time_stamper.set_submillis(SubmillisPrecision::Picoseconds, 5e8 as u32); assert_eq!( time_stamper.submillis_precision(), @@ -1633,14 +1680,14 @@ mod tests { #[test] fn read_stamp_with_ps_submillis_precision() { - let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); + let mut time_stamper = CdsTime::new_with_u16_days(0, 0); time_stamper.set_submillis(SubmillisPrecision::Picoseconds, 5e8 as u32); let mut write_buf: [u8; 16] = [0; 16]; let written = time_stamper .write_to_bytes(&mut write_buf) .expect("Writing timestamp failed"); assert_eq!(written, 11); - let stamp_deserialized = TimeProvider::::from_bytes(&write_buf); + let stamp_deserialized = CdsTime::::from_bytes(&write_buf); assert!(stamp_deserialized.is_ok()); let stamp_deserialized = stamp_deserialized.unwrap(); assert_eq!(stamp_deserialized.len_as_bytes(), 11); @@ -1653,14 +1700,14 @@ mod tests { #[test] fn read_stamp_with_us_submillis_precision() { - let mut time_stamper = TimeProvider::new_with_u16_days(0, 0); + let mut time_stamper = CdsTime::new_with_u16_days(0, 0); time_stamper.set_submillis(SubmillisPrecision::Microseconds, 500); let mut write_buf: [u8; 16] = [0; 16]; let written = time_stamper .write_to_bytes(&mut write_buf) .expect("Writing timestamp failed"); assert_eq!(written, 9); - let stamp_deserialized = TimeProvider::::from_bytes(&write_buf); + let stamp_deserialized = CdsTime::::from_bytes(&write_buf); assert!(stamp_deserialized.is_ok()); let stamp_deserialized = stamp_deserialized.unwrap(); assert_eq!(stamp_deserialized.len_as_bytes(), 9); @@ -1673,7 +1720,7 @@ mod tests { #[test] fn read_u24_stamp_with_us_submillis_precision() { - let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); + let mut time_stamper = CdsTime::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); time_stamper.set_submillis(SubmillisPrecision::Microseconds, 500); let mut write_buf: [u8; 16] = [0; 16]; let written = time_stamper @@ -1681,7 +1728,7 @@ mod tests { .expect("Writing timestamp failed"); // 1 byte pfield + 3 bytes days + 4 bytes ms of day + 2 bytes us precision assert_eq!(written, 10); - let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf); + let stamp_deserialized = CdsTime::from_bytes_with_u24_days(&write_buf); assert!(stamp_deserialized.is_ok()); let stamp_deserialized = stamp_deserialized.unwrap(); assert_eq!(stamp_deserialized.len_as_bytes(), 10); @@ -1695,7 +1742,7 @@ mod tests { #[test] fn read_u24_stamp_with_ps_submillis_precision() { - let mut time_stamper = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); + let mut time_stamper = CdsTime::new_with_u24_days(u16::MAX as u32 + 1, 0).unwrap(); time_stamper.set_submillis(SubmillisPrecision::Picoseconds, 5e8 as u32); let mut write_buf: [u8; 16] = [0; 16]; let written = time_stamper @@ -1703,7 +1750,7 @@ mod tests { .expect("Writing timestamp failed"); // 1 byte pfield + 3 bytes days + 4 bytes ms of day + 4 bytes us precision assert_eq!(written, 12); - let stamp_deserialized = TimeProvider::from_bytes_with_u24_days(&write_buf); + let stamp_deserialized = CdsTime::from_bytes_with_u24_days(&write_buf); assert!(stamp_deserialized.is_ok()); let stamp_deserialized = stamp_deserialized.unwrap(); assert_eq!(stamp_deserialized.len_as_bytes(), 12); @@ -1715,19 +1762,19 @@ mod tests { assert_eq!(stamp_deserialized.submillis(), 5e8 as u32); } - fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime { + fn generic_dt_case_0_no_prec(subsec_millis: u32) -> chrono::DateTime { NaiveDate::from_ymd_opt(2023, 1, 14) .unwrap() .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap() } fn generic_check_dt_case_0( - time_provider: &TimeProvider, + time_provider: &CdsTime, subsec_millis: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1736,16 +1783,16 @@ mod tests { time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis ); - assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + assert_eq!(time_provider.chrono_date_time().unwrap(), datetime_utc); } #[test] fn test_creation_from_dt_u16_days() { let subsec_millis = 250; let datetime_utc = generic_dt_case_0_no_prec(subsec_millis); - let time_provider = TimeProvider::from_dt_with_u16_days(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u16_days(&datetime_utc).unwrap(); generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc); - let time_provider_2: TimeProvider = + let time_provider_2: CdsTime = datetime_utc.try_into().expect("conversion failed"); // Test the TryInto trait impl assert_eq!(time_provider, time_provider_2); @@ -1754,29 +1801,29 @@ mod tests { fn test_creation_from_dt_u24_days() { let subsec_millis = 250; let datetime_utc = generic_dt_case_0_no_prec(subsec_millis); - let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u24_days(&datetime_utc).unwrap(); generic_check_dt_case_0(&time_provider, subsec_millis, datetime_utc); - let time_provider_2: TimeProvider = + let time_provider_2: CdsTime = datetime_utc.try_into().expect("conversion failed"); // Test the TryInto trait impl assert_eq!(time_provider, time_provider_2); } - fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime { + fn generic_dt_case_1_us_prec(subsec_millis: u32) -> chrono::DateTime { // 250 ms + 500 us let subsec_micros = subsec_millis * 1000 + 500; NaiveDate::from_ymd_opt(2023, 1, 14) .unwrap() .and_hms_micro_opt(16, 49, 30, subsec_micros) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap() } fn generic_check_dt_case_1_us_prec( - time_provider: &TimeProvider, + time_provider: &CdsTime, subsec_millis: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1790,15 +1837,14 @@ mod tests { SubmillisPrecision::Microseconds ); assert_eq!(time_provider.submillis(), 500); - assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + assert_eq!(time_provider.chrono_date_time().unwrap(), datetime_utc); } #[test] fn test_creation_from_dt_u16_days_us_prec() { let subsec_millis = 250; let datetime_utc = generic_dt_case_1_us_prec(subsec_millis); - let time_provider = - TimeProvider::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u16_days_us_precision(&datetime_utc).unwrap(); generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc); } @@ -1806,12 +1852,11 @@ mod tests { fn test_creation_from_dt_u24_days_us_prec() { let subsec_millis = 250; let datetime_utc = generic_dt_case_1_us_prec(subsec_millis); - let time_provider = - TimeProvider::from_dt_with_u24_days_us_precision(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u24_days_us_precision(&datetime_utc).unwrap(); generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc); } - fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (DateTime, u32) { + fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (chrono::DateTime, u32) { // 250 ms + 500 us let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; let submilli_nanos = subsec_nanos % 10_u32.pow(6); @@ -1820,17 +1865,17 @@ mod tests { .unwrap() .and_hms_nano_opt(16, 49, 30, subsec_nanos) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(), submilli_nanos, ) } fn generic_check_dt_case_2_ps_prec( - time_provider: &TimeProvider, + time_provider: &CdsTime, subsec_millis: u32, submilli_nanos: u32, - datetime_utc: DateTime, + datetime_utc: chrono::DateTime, ) { // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. @@ -1844,15 +1889,14 @@ mod tests { SubmillisPrecision::Picoseconds ); assert_eq!(time_provider.submillis(), submilli_nanos * 1000); - assert_eq!(time_provider.date_time().unwrap(), datetime_utc); + assert_eq!(time_provider.chrono_date_time().unwrap(), datetime_utc); } #[test] fn test_creation_from_dt_u16_days_ps_prec() { let subsec_millis = 250; let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis); - let time_provider = - TimeProvider::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u16_days_ps_precision(&datetime_utc).unwrap(); generic_check_dt_case_2_ps_prec( &time_provider, subsec_millis, @@ -1865,8 +1909,7 @@ mod tests { fn test_creation_from_dt_u24_days_ps_prec() { let subsec_millis = 250; let (datetime_utc, submilli_nanos) = generic_dt_case_2_ps_prec(subsec_millis); - let time_provider = - TimeProvider::from_dt_with_u24_days_ps_precision(&datetime_utc).unwrap(); + let time_provider = CdsTime::from_dt_with_u24_days_ps_precision(&datetime_utc).unwrap(); generic_check_dt_case_2_ps_prec( &time_provider, subsec_millis, @@ -1879,10 +1922,10 @@ mod tests { fn test_creation_from_unix_stamp_0_u16_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = CdsTime::from_unix_stamp_with_u16_days( + &UnixTime::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) } @@ -1891,10 +1934,10 @@ mod tests { fn test_creation_from_unix_stamp_0_u24_days() { let unix_secs = 0; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u24_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = CdsTime::from_unix_stamp_with_u24_days( + &UnixTime::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32) } @@ -1906,10 +1949,13 @@ mod tests { .unwrap() .and_hms_milli_opt(16, 49, 30, subsec_millis) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(); - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&datetime_utc.into()) - .expect("creating provider from unix stamp failed"); + let time_provider = CdsTime::from_unix_stamp_with_u16_days( + &datetime_utc.into(), + SubmillisPrecision::Absent, + ) + .expect("creating provider from unix stamp failed"); // https://www.timeanddate.com/date/durationresult.html?d1=01&m1=01&y1=1958&d2=14&m2=01&y2=2023 // Leap years need to be accounted for as well. assert_eq!(time_provider.ccsds_days, 23754); @@ -1917,7 +1963,7 @@ mod tests { time_provider.ms_of_day, 30 * 1000 + 49 * 60 * 1000 + 16 * 60 * 60 * 1000 + subsec_millis ); - let dt_back = time_provider.date_time().unwrap(); + let dt_back = time_provider.chrono_date_time().unwrap(); assert_eq!(datetime_utc, dt_back); } @@ -1925,10 +1971,10 @@ mod tests { fn test_creation_0_ccsds_days() { let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let subsec_millis = 0; - let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs, - subsec_millis, - )) + let time_provider = CdsTime::from_unix_stamp_with_u16_days( + &UnixTime::new(unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) .expect("creating provider from unix stamp failed"); assert_eq!(time_provider.ccsds_days, 0) } @@ -1937,10 +1983,10 @@ mod tests { fn test_invalid_creation_from_unix_stamp_days_too_large() { let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - invalid_unix_secs, - subsec_millis, - )) { + match CdsTime::from_unix_stamp_with_u16_days( + &UnixTime::new(invalid_unix_secs, subsec_millis), + SubmillisPrecision::Absent, + ) { Ok(_) => { panic!("creation should not succeed") } @@ -1964,21 +2010,26 @@ mod tests { // precisely 31-12-1957 23:59:55 let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let subsec_millis = 0; - match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( - unix_secs as i64, - subsec_millis, - )) { + match CdsTime::from_unix_stamp_with_u16_days( + &UnixTime::new(unix_secs as i64, subsec_millis), + SubmillisPrecision::Absent, + ) { Ok(_) => { panic!("creation should not succeed") } Err(e) => { if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { - assert_eq!(dt.year(), 1957); - assert_eq!(dt.month(), 12); - assert_eq!(dt.day(), 31); - assert_eq!(dt.hour(), 23); - assert_eq!(dt.minute(), 59); - assert_eq!(dt.second(), 55); + let dt = dt.chrono_date_time(); + if let chrono::LocalResult::Single(dt) = dt { + assert_eq!(dt.year(), 1957); + assert_eq!(dt.month(), 12); + assert_eq!(dt.day(), 31); + assert_eq!(dt.hour(), 23); + assert_eq!(dt.minute(), 59); + assert_eq!(dt.second(), 55); + } else { + panic!("unexpected error {}", e) + } } else { panic!("unexpected error {}", e) } @@ -1988,7 +2039,7 @@ mod tests { #[test] fn test_addition_u16_days_day_increment() { - let mut provider = TimeProvider::new_with_u16_days(0, MS_PER_DAY - 5 * 1000); + let mut provider = CdsTime::new_with_u16_days(0, MS_PER_DAY - 5 * 1000); let seconds_offset = Duration::from_secs(10); assert_eq!(provider.ccsds_days, 0); assert_eq!(provider.ms_of_day, MS_PER_DAY - 5 * 1000); @@ -1999,7 +2050,7 @@ mod tests { #[test] fn test_addition_u16_days() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); let seconds_offset = Duration::from_secs(5); assert_eq!(provider.ccsds_days, 0); assert_eq!(provider.ms_of_day, 0); @@ -2013,7 +2064,7 @@ mod tests { #[test] fn test_addition_u24_days() { - let mut provider = TimeProvider::new_with_u24_days(u16::MAX as u32, 0).unwrap(); + let mut provider = CdsTime::new_with_u24_days(u16::MAX as u32, 0).unwrap(); let seconds_offset = Duration::from_secs(5); assert_eq!(provider.ccsds_days, u16::MAX as u32); assert_eq!(provider.ms_of_day, 0); @@ -2027,13 +2078,13 @@ mod tests { #[test] fn test_dyn_creation_u24_days() { - let stamp = TimeProvider::new_with_u24_days(u16::MAX as u32 + 1, 24).unwrap(); + let stamp = CdsTime::new_with_u24_days(u16::MAX as u32 + 1, 24).unwrap(); let mut buf: [u8; 32] = [0; 32]; stamp.write_to_bytes(&mut buf).unwrap(); let dyn_provider = get_dyn_time_provider_from_bytes(&buf); assert!(dyn_provider.is_ok()); let dyn_provider = dyn_provider.unwrap(); - assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCodes::Cds); + assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCode::Cds); assert_eq!(dyn_provider.ccsds_days_as_u32(), u16::MAX as u32 + 1); assert_eq!(dyn_provider.ms_of_day(), 24); assert_eq!( @@ -2048,7 +2099,7 @@ mod tests { #[test] fn test_addition_with_us_precision_u16_days() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); provider.set_submillis(SubmillisPrecision::Microseconds, 0); let duration = Duration::from_micros(500); provider += duration; @@ -2061,7 +2112,7 @@ mod tests { #[test] fn test_addition_with_us_precision_u16_days_with_subsec_millis() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); provider.set_submillis(SubmillisPrecision::Microseconds, 0); let duration = Duration::from_micros(1200); provider += duration; @@ -2075,7 +2126,7 @@ mod tests { #[test] fn test_addition_with_us_precision_u16_days_carry_over() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); provider.set_submillis(SubmillisPrecision::Microseconds, 800); let duration = Duration::from_micros(400); provider += duration; @@ -2090,7 +2141,7 @@ mod tests { #[test] fn test_addition_with_ps_precision_u16_days() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); provider.set_submillis(SubmillisPrecision::Picoseconds, 0); // 500 us as ns let duration = Duration::from_nanos(500 * 10u32.pow(3) as u64); @@ -2107,7 +2158,7 @@ mod tests { fn test_addition_on_ref() { // This test case also tests the case where there is no submillis precision but subsecond // milliseconds. - let provider_ref = &TimeProvider::new_with_u16_days(2, 500); + let provider_ref = &CdsTime::new_with_u16_days(2, 500); let new_stamp = provider_ref + Duration::from_millis(2 * 24 * 60 * 60 * 1000 + 500); assert_eq!(new_stamp.ccsds_days_as_u32(), 4); assert_eq!(new_stamp.ms_of_day, 1000); @@ -2123,7 +2174,7 @@ mod tests { } #[test] fn test_addition_with_ps_precision_u16_days_with_subsec_millis() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); provider.set_submillis(SubmillisPrecision::Picoseconds, 0); // 1200 us as ns let duration = Duration::from_nanos(1200 * 10u32.pow(3) as u64); @@ -2138,7 +2189,7 @@ mod tests { #[test] fn test_addition_with_ps_precision_u16_days_carryover() { - let mut provider = TimeProvider::new_with_u16_days(0, 0); + let mut provider = CdsTime::new_with_u16_days(0, 0); // 800 us as ps provider.set_submillis(SubmillisPrecision::Picoseconds, 800 * 10_u32.pow(6)); // 400 us as ns @@ -2154,14 +2205,14 @@ mod tests { #[test] fn test_dyn_creation_u16_days_with_precision() { - let mut stamp = TimeProvider::new_with_u16_days(24, 24); + let mut stamp = CdsTime::new_with_u16_days(24, 24); stamp.set_submillis(SubmillisPrecision::Microseconds, 666); let mut buf: [u8; 32] = [0; 32]; stamp.write_to_bytes(&mut buf).unwrap(); let dyn_provider = get_dyn_time_provider_from_bytes(&buf); assert!(dyn_provider.is_ok()); let dyn_provider = dyn_provider.unwrap(); - assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCodes::Cds); + assert_eq!(dyn_provider.ccdsd_time_code(), CcsdsTimeCode::Cds); assert_eq!(dyn_provider.ccsds_days_as_u32(), 24); assert_eq!(dyn_provider.ms_of_day(), 24); assert_eq!( @@ -2177,7 +2228,7 @@ mod tests { #[test] fn test_new_u24_days_too_large() { - let time_provider = TimeProvider::new_with_u24_days(2_u32.pow(24), 0); + let time_provider = CdsTime::new_with_u24_days(2_u32.pow(24), 0); assert!(time_provider.is_err()); let e = time_provider.unwrap_err(); if let CdsError::InvalidCcsdsDays(days) = e { @@ -2194,21 +2245,21 @@ mod tests { .unwrap() .and_hms_milli_opt(23, 59, 59, 999) .unwrap() - .and_local_timezone(Utc) + .and_local_timezone(chrono::Utc) .unwrap(); - let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc); + let time_provider = CdsTime::from_dt_with_u24_days(&datetime_utc); assert!(time_provider.is_err()); if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_err() { - assert_eq!(dt, datetime_utc); + assert_eq!(dt, datetime_utc.into()); } } #[test] fn test_eq() { - let stamp0 = TimeProvider::new_with_u16_days(0, 0); + let stamp0 = CdsTime::new_with_u16_days(0, 0); let mut buf: [u8; 7] = [0; 7]; stamp0.write_to_bytes(&mut buf).unwrap(); - let stamp1 = TimeProvider::from_bytes_with_u16_days(&buf).unwrap(); + let stamp1 = CdsTime::from_bytes_with_u16_days(&buf).unwrap(); assert_eq!(stamp0, stamp1); assert!(stamp0 >= stamp1); assert!(stamp1 <= stamp0); @@ -2216,11 +2267,11 @@ mod tests { #[test] fn test_ord() { - let stamp0 = TimeProvider::new_with_u24_days(0, 0).unwrap(); - let stamp1 = TimeProvider::new_with_u24_days(0, 50000).unwrap(); - let mut stamp2 = TimeProvider::new_with_u24_days(0, 50000).unwrap(); + let stamp0 = CdsTime::new_with_u24_days(0, 0).unwrap(); + let stamp1 = CdsTime::new_with_u24_days(0, 50000).unwrap(); + let mut stamp2 = CdsTime::new_with_u24_days(0, 50000).unwrap(); stamp2.set_submillis(SubmillisPrecision::Microseconds, 500); - let stamp3 = TimeProvider::new_with_u24_days(1, 0).unwrap(); + let stamp3 = CdsTime::new_with_u24_days(1, 0).unwrap(); assert!(stamp1 > stamp0); assert!(stamp2 > stamp0); assert!(stamp2 > stamp1); @@ -2231,8 +2282,8 @@ mod tests { #[test] fn test_conversion() { - let mut stamp_small = TimeProvider::new_with_u16_days(u16::MAX, 500); - let stamp_larger: TimeProvider = stamp_small.into(); + let mut stamp_small = CdsTime::new_with_u16_days(u16::MAX, 500); + let stamp_larger: CdsTime = stamp_small.into(); assert_eq!(stamp_larger.ccsds_days_as_u32(), u16::MAX as u32); assert_eq!(stamp_larger.ms_of_day(), 500); stamp_small = stamp_larger.try_into().unwrap(); @@ -2242,25 +2293,25 @@ mod tests { #[test] fn test_update_from_now() { - let mut stamp = TimeProvider::new_with_u16_days(0, 0); + let mut stamp = CdsTime::new_with_u16_days(0, 0); let _ = stamp.update_from_now(); - let dt = stamp.unix_stamp().as_date_time().unwrap(); + let dt = stamp.unix_stamp().chrono_date_time().unwrap(); assert!(dt.year() > 2020); } #[test] fn test_setting_submillis_precision() { - let mut provider = TimeProvider::new_with_u16_days(0, 15); + let mut provider = CdsTime::new_with_u16_days(0, 15); provider.set_submillis(SubmillisPrecision::Microseconds, 500); } #[test] #[cfg(feature = "serde")] fn test_serialization() { - let stamp_now = TimeProvider::from_now_with_u16_days().expect("Error retrieving time"); + let stamp_now = CdsTime::from_now_with_u16_days().expect("Error retrieving time"); let val = to_allocvec(&stamp_now).expect("Serializing timestamp failed"); assert!(val.len() > 0); - let stamp_deser: TimeProvider = from_bytes(&val).expect("Stamp deserialization failed"); + let stamp_deser: CdsTime = from_bytes(&val).expect("Stamp deserialization failed"); assert_eq!(stamp_deser, stamp_now); } @@ -2277,7 +2328,7 @@ mod tests { #[test] fn test_stamp_to_vec_u16() { - let stamp = TimeProvider::new_with_u16_days(1, 1); + let stamp = CdsTime::new_with_u16_days(1, 1); let stamp_vec = stamp.to_vec().unwrap(); let mut buf: [u8; 7] = [0; 7]; stamp.write_to_bytes(&mut buf).unwrap(); @@ -2286,10 +2337,23 @@ mod tests { #[test] fn test_stamp_to_vec_u24() { - let stamp = TimeProvider::new_with_u24_days(1, 1).unwrap(); + let stamp = CdsTime::new_with_u24_days(1, 1).unwrap(); let stamp_vec = stamp.to_vec().unwrap(); let mut buf: [u8; 10] = [0; 10]; stamp.write_to_bytes(&mut buf).unwrap(); assert_eq!(stamp_vec, buf[..stamp.len_written()]); } + + #[test] + #[cfg(feature = "timelib")] + fn test_timelib_stamp() { + let stamp = CdsTime::new_with_u16_days(0, 0); + let timelib_dt = stamp.timelib_date_time().unwrap(); + assert_eq!(timelib_dt.year(), 1958); + assert_eq!(timelib_dt.month(), time::Month::January); + assert_eq!(timelib_dt.day(), 1); + assert_eq!(timelib_dt.hour(), 0); + assert_eq!(timelib_dt.minute(), 0); + assert_eq!(timelib_dt.second(), 0); + } } diff --git a/src/time/cuc.rs b/src/time/cuc.rs index 4b5d6e8..66f4cf6 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -2,17 +2,37 @@ //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.2 . //! //! The core data structure to do this is the [TimeProviderCcsdsEpoch] struct. -use super::*; -use chrono::Datelike; -use core::fmt::Debug; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use core::fmt::{Debug, Display, Formatter}; use core::ops::{Add, AddAssign}; use core::time::Duration; use core::u64; +use crate::ByteConversionError; + +#[cfg(feature = "std")] +use super::StdTimestampError; +use super::{ + ccsds_epoch_to_unix_epoch, unix_epoch_to_ccsds_epoch, CcsdsTimeCode, CcsdsTimeProvider, + TimeReader, TimeWriter, TimestampError, UnixTime, +}; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::time::SystemTime; + +#[cfg(feature = "chrono")] +use chrono::Datelike; + +#[cfg(feature = "alloc")] +use super::ccsds_time_code_from_p_field; + const MIN_CUC_LEN: usize = 2; /// Base value for the preamble field for a time field parser to determine the time field type. -pub const P_FIELD_BASE: u8 = (CcsdsTimeCodes::CucCcsdsEpoch as u8) << 4; +pub const P_FIELD_BASE: u8 = (CcsdsTimeCode::CucCcsdsEpoch as u8) << 4; /// Maximum length if the preamble field is not extended. pub const MAX_CUC_LEN_SMALL_PREAMBLE: usize = 8; @@ -103,6 +123,7 @@ pub enum CucError { resolution: FractionalResolution, value: u64, }, + LeapSecondCorrectionError, } impl Display for CucError { @@ -120,6 +141,9 @@ impl Display for CucError { "invalid cuc fractional part {value} for resolution {resolution:?}" ) } + CucError::LeapSecondCorrectionError => { + write!(f, "error while correcting for leap seconds") + } } } } @@ -147,14 +171,20 @@ pub struct FractionalPart(FractionalResolution, u32); /// type (generally seconds) to 4 bytes and the width of the fractions type to 3 bytes. This limits /// the maximum time stamp size to [MAX_CUC_LEN_SMALL_PREAMBLE] (8 bytes). /// +/// Please note that this object does not implement the [CcsdsTimeProvider] trait by itself because +/// leap seconds corrections need to be applied to support the trait methods. Instead, it needs +/// to be converted to a [CucTimeWithLeapSecs] object using the [Self::to_leap_sec_helper] method. +/// /// # Example /// /// ``` -/// use spacepackets::time::cuc::{FractionalResolution, TimeProviderCcsdsEpoch}; +/// use spacepackets::time::cuc::{FractionalResolution, CucTime}; /// use spacepackets::time::{TimeWriter, CcsdsTimeCodes, TimeReader, CcsdsTimeProvider}; /// +/// const LEAP_SECONDS: u32 = 37; /// // Highest fractional resolution -/// let timestamp_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs).expect("creating cuc stamp failed"); +/// let timestamp_now = CucTime::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS) +/// .expect("creating cuc stamp failed"); /// let mut raw_stamp = [0; 16]; /// { /// let written = timestamp_now.write_to_bytes(&mut raw_stamp).expect("writing timestamp failed"); @@ -163,7 +193,7 @@ pub struct FractionalPart(FractionalResolution, u32); /// assert_eq!(written, 8); /// } /// { -/// let read_result = TimeProviderCcsdsEpoch::from_bytes(&raw_stamp); +/// let read_result = TimeProviderCcsdsEpoch::from_bytes_with_leap_seconds(&raw_stamp, LEAP_SECONDS); /// assert!(read_result.is_ok()); /// let stamp_deserialized = read_result.unwrap(); /// assert_eq!(stamp_deserialized, timestamp_now); @@ -171,12 +201,27 @@ pub struct FractionalPart(FractionalResolution, u32); /// ``` #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TimeProviderCcsdsEpoch { +pub struct CucTime { pfield: u8, counter: WidthCounterPair, fractions: Option, } +/// This object is a wrapper object around the [CucTime] object which also tracks +/// the leap seconds. This is necessary to implement the [CcsdsTimeProvider] trait. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CucTimeWithLeapSecs { + pub time: CucTime, + pub leap_seconds: u32, +} + +impl CucTimeWithLeapSecs { + pub fn new(time: CucTime, leap_seconds: u32) -> Self { + Self { time, leap_seconds } + } +} + #[inline] pub fn pfield_len(pfield: u8) -> usize { if ((pfield >> 7) & 0b1) == 1 { @@ -185,7 +230,7 @@ pub fn pfield_len(pfield: u8) -> usize { 1 } -impl TimeProviderCcsdsEpoch { +impl CucTime { /// Create a time provider with a four byte counter and no fractional part. pub fn new(counter: u32) -> Self { // These values are definitely valid, so it is okay to unwrap here. @@ -239,11 +284,21 @@ impl TimeProviderCcsdsEpoch { /// This function will return the current time as a CUC timestamp. /// The counter width will always be set to 4 bytes because the normal CCSDS epoch will overflow /// when using less than that. + /// + /// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections + /// must be applied on top of the UTC based time retrieved from the system in addition to the + /// conversion to the CCSDS epoch. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn from_now(fraction_resolution: FractionalResolution) -> Result { + pub fn from_now( + fraction_resolution: FractionalResolution, + leap_seconds: u32, + ) -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64); + ccsds_epoch + .checked_add(i64::from(leap_seconds)) + .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; if fraction_resolution == FractionalResolution::Seconds { return Ok(Self::new(ccsds_epoch as u32)); } @@ -255,11 +310,19 @@ impl TimeProviderCcsdsEpoch { /// Updates the current time stamp from the current time. The fractional field width remains /// the same and will be updated accordingly. + /// + /// The CUC timestamp uses TAI as the reference time system. Therefore, leap second corrections + /// must be applied on top of the UTC based time retrieved from the system in addition to the + /// conversion to the CCSDS epoch. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - pub fn update_from_now(&mut self) -> Result<(), StdTimestampError> { + pub fn update_from_now(&mut self, leap_seconds: u32) -> Result<(), StdTimestampError> { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; self.counter.1 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32; + self.counter + .1 + .checked_add(leap_seconds) + .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; if self.fractions.is_some() { self.fractions = fractional_part_from_subsec_ns( self.fractions.unwrap().0, @@ -269,16 +332,23 @@ impl TimeProviderCcsdsEpoch { Ok(()) } - pub fn from_date_time( - dt: &DateTime, + #[cfg(feature = "chrono")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] + pub fn from_chrono_date_time( + dt: &chrono::DateTime, res: FractionalResolution, + leap_seconds: u32, ) -> Result { // Year before CCSDS epoch is invalid. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt))); } + let counter = dt + .timestamp() + .checked_add(i64::from(leap_seconds)) + .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; Self::new_generic( - WidthCounterPair(4, dt.timestamp() as u32), + WidthCounterPair(4, counter as u32), fractional_part_from_subsec_ns(res, dt.timestamp_subsec_nanos() as u64), ) .map_err(|e| e.into()) @@ -287,20 +357,20 @@ impl TimeProviderCcsdsEpoch { /// Generates a CUC timestamp from a UNIX timestamp with a width of 4. This width is able /// to accomodate all possible UNIX timestamp values. pub fn from_unix_stamp( - unix_stamp: &UnixTimestamp, + unix_stamp: &UnixTime, res: FractionalResolution, + leap_seconds: u32, ) -> Result { - let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds); + let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.secs); // Negative CCSDS epoch is invalid. if ccsds_epoch < 0 { - return Err(TimestampError::DateBeforeCcsdsEpoch( - unix_stamp.as_date_time().unwrap(), - )); + return Err(TimestampError::DateBeforeCcsdsEpoch(*unix_stamp)); } - let fractions = fractional_part_from_subsec_ns( - res, - unix_stamp.subsecond_millis() as u64 * 10_u64.pow(6), - ); + ccsds_epoch + .checked_add(i64::from(leap_seconds)) + .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; + let fractions = + fractional_part_from_subsec_ns(res, unix_stamp.subsec_millis() as u64 * 10_u64.pow(6)); Self::new_generic(WidthCounterPair(4, ccsds_epoch as u32), fractions).map_err(|e| e.into()) } @@ -309,6 +379,10 @@ impl TimeProviderCcsdsEpoch { Self::new_generic(WidthCounterPair(2, counter as u32), None).unwrap() } + pub fn ccsds_time_code(&self) -> CcsdsTimeCode { + CcsdsTimeCode::CucCcsdsEpoch + } + pub fn width_counter_pair(&self) -> WidthCounterPair { self.counter } @@ -325,6 +399,10 @@ impl TimeProviderCcsdsEpoch { self.fractions } + pub fn to_leap_sec_helper(&self, leap_seconds: u32) -> CucTimeWithLeapSecs { + CucTimeWithLeapSecs::new(*self, leap_seconds) + } + pub fn set_fractions(&mut self, fractions: FractionalPart) -> Result<(), CucError> { Self::verify_fractions_value(fractions)?; self.fractions = Some(fractions); @@ -410,8 +488,15 @@ impl TimeProviderCcsdsEpoch { } #[inline] - fn unix_seconds(&self) -> i64 { + pub fn unix_secs(&self, leap_seconds: u32) -> i64 { ccsds_epoch_to_unix_epoch(self.counter.1 as i64) + .checked_sub(leap_seconds as i64) + .unwrap() + } + + #[inline] + pub fn subsec_millis(&self) -> u16 { + (self.subsec_nanos() / 1_000_000) as u16 } /// This returns the length of the individual components of the CUC timestamp in addition @@ -455,13 +540,25 @@ impl TimeProviderCcsdsEpoch { } Ok(()) } + + fn len_as_bytes(&self) -> usize { + Self::len_packed_from_pfield(self.pfield) + } + + fn subsec_nanos(&self) -> u32 { + if let Some(fractions) = self.fractions { + if fractions.0 == FractionalResolution::Seconds { + return 0; + } + // Rounding down here is the correct approach. + return convert_fractional_part_to_ns(fractions) as u32; + } + 0 + } } -impl TimeReader for TimeProviderCcsdsEpoch { - fn from_bytes(buf: &[u8]) -> Result - where - Self: Sized, - { +impl TimeReader for CucTime { + fn from_bytes(buf: &[u8]) -> Result { if buf.len() < MIN_CUC_LEN { return Err(TimestampError::ByteConversion( ByteConversionError::FromSliceTooSmall { @@ -472,16 +569,16 @@ impl TimeReader for TimeProviderCcsdsEpoch { } match ccsds_time_code_from_p_field(buf[0]) { Ok(code) => { - if code != CcsdsTimeCodes::CucCcsdsEpoch { + if code != CcsdsTimeCode::CucCcsdsEpoch { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::CucCcsdsEpoch, + expected: CcsdsTimeCode::CucCcsdsEpoch, found: code as u8, }); } } Err(raw) => { return Err(TimestampError::InvalidTimeCode { - expected: CcsdsTimeCodes::CucCcsdsEpoch, + expected: CcsdsTimeCode::CucCcsdsEpoch, found: raw, }); } @@ -541,7 +638,7 @@ impl TimeReader for TimeProviderCcsdsEpoch { } } -impl TimeWriter for TimeProviderCcsdsEpoch { +impl TimeWriter for CucTime { fn write_to_bytes(&self, bytes: &mut [u8]) -> Result { // Cross check the sizes of the counters against byte widths in the ctor if bytes.len() < self.len_as_bytes() { @@ -593,50 +690,32 @@ impl TimeWriter for TimeProviderCcsdsEpoch { } } -impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { +impl CcsdsTimeProvider for CucTimeWithLeapSecs { fn len_as_bytes(&self) -> usize { - Self::len_packed_from_pfield(self.pfield) + self.time.len_as_bytes() } fn p_field(&self) -> (usize, [u8; 2]) { - (1, [self.pfield, 0]) + (1, [self.time.pfield, 0]) } - fn ccdsd_time_code(&self) -> CcsdsTimeCodes { - CcsdsTimeCodes::CucCcsdsEpoch + fn ccdsd_time_code(&self) -> CcsdsTimeCode { + self.time.ccsds_time_code() } - fn unix_seconds(&self) -> i64 { - self.unix_seconds() + fn unix_secs(&self) -> i64 { + ccsds_epoch_to_unix_epoch(self.time.counter.1 as i64) + .checked_sub(self.leap_seconds as i64) + .unwrap() } - fn subsecond_millis(&self) -> u16 { - if let Some(fractions) = self.fractions { - if fractions.0 == FractionalResolution::Seconds { - return 0; - } - // Rounding down here is the correct approach. - return (convert_fractional_part_to_ns(fractions) / 10_u32.pow(6) as u64) as u16; - } - 0 - } - - fn date_time(&self) -> Option> { - let unix_seconds = self.unix_seconds(); - let ns = if let Some(fractional_part) = self.fractions { - convert_fractional_part_to_ns(fractional_part) - } else { - 0 - }; - if let LocalResult::Single(res) = Utc.timestamp_opt(unix_seconds, ns as u32) { - return Some(res); - } - None + fn subsec_nanos(&self) -> u32 { + self.time.subsec_nanos() } } fn get_provider_values_after_duration_addition( - provider: &TimeProviderCcsdsEpoch, + provider: &CucTime, duration: Duration, ) -> (u32, Option) { let mut new_counter = provider.counter.1; @@ -685,7 +764,7 @@ fn get_provider_values_after_duration_addition( (new_counter, fractional_part) } -impl AddAssign for TimeProviderCcsdsEpoch { +impl AddAssign for CucTime { fn add_assign(&mut self, duration: Duration) { let (new_counter, new_fractional_part) = get_provider_values_after_duration_addition(self, duration); @@ -696,7 +775,7 @@ impl AddAssign for TimeProviderCcsdsEpoch { } } -impl Add for TimeProviderCcsdsEpoch { +impl Add for CucTime { type Output = Self; fn add(self, duration: Duration) -> Self::Output { @@ -710,8 +789,8 @@ impl Add for TimeProviderCcsdsEpoch { } } -impl Add for &TimeProviderCcsdsEpoch { - type Output = TimeProviderCcsdsEpoch; +impl Add for &CucTime { + type Output = CucTime; fn add(self, duration: Duration) -> Self::Output { let (new_counter, new_fractional_part) = @@ -726,44 +805,50 @@ impl Add for &TimeProviderCcsdsEpoch { #[cfg(test)] mod tests { + use crate::time::{UnixTime, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY}; + use super::*; use alloc::string::ToString; - use chrono::{Datelike, Timelike}; + use chrono::{Datelike, TimeZone, Timelike}; #[allow(unused_imports)] use std::println; + const LEAP_SECONDS: u32 = 37; + #[test] fn test_basic_zero_epoch() { - let zero_cuc = TimeProviderCcsdsEpoch::new(0); + // Do not include leap second corrections, which do not apply to dates before 1972. + let zero_cuc = CucTime::new(0); assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(zero_cuc.width(), zero_cuc.width_counter_pair().0); assert_eq!(zero_cuc.counter(), zero_cuc.width_counter_pair().1); - assert_eq!(zero_cuc.ccdsd_time_code(), CcsdsTimeCodes::CucCcsdsEpoch); + let ccsds_cuc = zero_cuc.to_leap_sec_helper(0); + assert_eq!(ccsds_cuc.ccdsd_time_code(), CcsdsTimeCode::CucCcsdsEpoch); let counter = zero_cuc.width_counter_pair(); assert_eq!(counter.0, 4); assert_eq!(counter.1, 0); let fractions = zero_cuc.width_fractions_pair(); assert!(fractions.is_none()); - let dt = zero_cuc.date_time(); - assert!(dt.is_some()); - let dt = dt.unwrap(); - assert_eq!(dt.year(), 1958); - assert_eq!(dt.month(), 1); - assert_eq!(dt.day(), 1); - assert_eq!(dt.hour(), 0); - assert_eq!(dt.minute(), 0); - assert_eq!(dt.second(), 0); + let dt = ccsds_cuc.chrono_date_time(); + if let chrono::LocalResult::Single(dt) = dt { + assert_eq!(dt.year(), 1958); + assert_eq!(dt.month(), 1); + assert_eq!(dt.day(), 1); + assert_eq!(dt.hour(), 0); + assert_eq!(dt.minute(), 0); + assert_eq!(dt.second(), 0); + } } #[test] fn test_write_no_fractions() { let mut buf: [u8; 16] = [0; 16]; - let zero_cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None); + let zero_cuc = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None); assert!(zero_cuc.is_ok()); let zero_cuc = zero_cuc.unwrap(); let res = zero_cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(zero_cuc.subsecond_millis(), 0); + assert_eq!(zero_cuc.subsec_nanos(), 0); assert_eq!(zero_cuc.len_as_bytes(), 5); assert_eq!(pfield_len(buf[0]), 1); let written = res.unwrap(); @@ -771,7 +856,7 @@ mod tests { assert_eq!((buf[0] >> 7) & 0b1, 0); let time_code = ccsds_time_code_from_p_field(buf[0]); assert!(time_code.is_ok()); - assert_eq!(time_code.unwrap(), CcsdsTimeCodes::CucCcsdsEpoch); + assert_eq!(time_code.unwrap(), CcsdsTimeCode::CucCcsdsEpoch); assert_eq!((buf[0] >> 2) & 0b11, 0b11); assert_eq!(buf[0] & 0b11, 0); let raw_counter = u32::from_be_bytes(buf[1..5].try_into().unwrap()); @@ -781,27 +866,28 @@ mod tests { #[test] fn test_datetime_now() { - let now = Utc::now(); - let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs); + let now = chrono::Utc::now(); + let cuc_now = CucTime::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS); assert!(cuc_now.is_ok()); let cuc_now = cuc_now.unwrap(); - let dt_opt = cuc_now.date_time(); - assert!(dt_opt.is_some()); - let dt = dt_opt.unwrap(); - let diff = dt - now; - assert!(diff.num_milliseconds() < 1000); - println!("datetime from cuc: {}", dt); - println!("datetime now: {}", now); + let ccsds_cuc = cuc_now.to_leap_sec_helper(LEAP_SECONDS); + let dt_opt = ccsds_cuc.chrono_date_time(); + if let chrono::LocalResult::Single(dt) = dt_opt { + let diff = dt - now; + assert!(diff.num_milliseconds() < 1000); + println!("datetime from cuc: {}", dt); + println!("datetime now: {}", now); + } else { + panic!("date time creation from now failed") + } } #[test] fn test_read_no_fractions() { let mut buf: [u8; 16] = [0; 16]; - let zero_cuc = - TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap(); + let zero_cuc = CucTime::new_generic(WidthCounterPair(4, 0x20102030), None).unwrap(); zero_cuc.write_to_bytes(&mut buf).unwrap(); - let cuc_read_back = - TimeProviderCcsdsEpoch::from_bytes(&buf).expect("reading cuc timestamp failed"); + let cuc_read_back = CucTime::from_bytes(&buf).expect("reading cuc timestamp failed"); assert_eq!(cuc_read_back, zero_cuc); assert_eq!(cuc_read_back.width_counter_pair().1, 0x20102030); assert_eq!(cuc_read_back.width_fractions_pair(), None); @@ -811,7 +897,7 @@ mod tests { fn invalid_read_len() { let mut buf: [u8; 16] = [0; 16]; for i in 0..2 { - let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]); + let res = CucTime::from_bytes(&buf[0..i]); assert!(res.is_err()); let err = res.unwrap_err(); if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall { @@ -823,10 +909,10 @@ mod tests { assert_eq!(expected, 2); } } - let large_stamp = TimeProviderCcsdsEpoch::new_with_fine_fractions(22, 300).unwrap(); + let large_stamp = CucTime::new_with_fine_fractions(22, 300).unwrap(); large_stamp.write_to_bytes(&mut buf).unwrap(); for i in 2..large_stamp.len_as_bytes() - 1 { - let res = TimeProviderCcsdsEpoch::from_bytes(&buf[0..i]); + let res = CucTime::from_bytes(&buf[0..i]); assert!(res.is_err()); let err = res.unwrap_err(); if let TimestampError::ByteConversion(ByteConversionError::FromSliceTooSmall { @@ -843,7 +929,7 @@ mod tests { #[test] fn write_and_read_tiny_stamp() { let mut buf = [0; 2]; - let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 200), None); + let cuc = CucTime::new_generic(WidthCounterPair(1, 200), None); assert!(cuc.is_ok()); let cuc = cuc.unwrap(); assert_eq!(cuc.len_as_bytes(), 2); @@ -852,7 +938,7 @@ mod tests { let written = res.unwrap(); assert_eq!(written, 2); assert_eq!(buf[1], 200); - let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); + let cuc_read_back = CucTime::from_bytes(&buf); assert!(cuc_read_back.is_ok()); let cuc_read_back = cuc_read_back.unwrap(); assert_eq!(cuc_read_back, cuc); @@ -861,7 +947,7 @@ mod tests { #[test] fn write_slightly_larger_stamp() { let mut buf = [0; 4]; - let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(2, 40000), None); + let cuc = CucTime::new_generic(WidthCounterPair(2, 40000), None); assert!(cuc.is_ok()); let cuc = cuc.unwrap(); assert_eq!(cuc.len_as_bytes(), 3); @@ -870,7 +956,7 @@ mod tests { let written = res.unwrap(); assert_eq!(written, 3); assert_eq!(u16::from_be_bytes(buf[1..3].try_into().unwrap()), 40000); - let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); + let cuc_read_back = CucTime::from_bytes(&buf); assert!(cuc_read_back.is_ok()); let cuc_read_back = cuc_read_back.unwrap(); assert_eq!(cuc_read_back, cuc); @@ -882,7 +968,7 @@ mod tests { #[test] fn write_read_three_byte_cntr_stamp() { let mut buf = [0; 4]; - let cuc = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None); + let cuc = CucTime::new_generic(WidthCounterPair(3, 2_u32.pow(24) - 2), None); assert!(cuc.is_ok()); let cuc = cuc.unwrap(); assert_eq!(cuc.len_as_bytes(), 4); @@ -893,7 +979,7 @@ mod tests { let mut temp_buf = [0; 4]; temp_buf[1..4].copy_from_slice(&buf[1..4]); assert_eq!(u32::from_be_bytes(temp_buf), 2_u32.pow(24) - 2); - let cuc_read_back = TimeProviderCcsdsEpoch::from_bytes(&buf); + let cuc_read_back = CucTime::from_bytes(&buf); assert!(cuc_read_back.is_ok()); let cuc_read_back = cuc_read_back.unwrap(); assert_eq!(cuc_read_back, cuc); @@ -902,7 +988,7 @@ mod tests { #[test] fn test_write_invalid_buf() { let mut buf: [u8; 16] = [0; 16]; - let res = TimeProviderCcsdsEpoch::new_with_fine_fractions(0, 0); + let res = CucTime::new_with_fine_fractions(0, 0); let cuc = res.unwrap(); for i in 0..cuc.len_as_bytes() - 1 { let err = cuc.write_to_bytes(&mut buf[0..i]); @@ -923,13 +1009,13 @@ mod tests { #[test] fn invalid_ccsds_stamp_type() { let mut buf: [u8; 16] = [0; 16]; - buf[0] |= (CcsdsTimeCodes::CucAgencyEpoch as u8) << 4; - let res = TimeProviderCcsdsEpoch::from_bytes(&buf); + buf[0] |= (CcsdsTimeCode::CucAgencyEpoch as u8) << 4; + let res = CucTime::from_bytes(&buf); assert!(res.is_err()); let err = res.unwrap_err(); if let TimestampError::InvalidTimeCode { expected, found } = err { - assert_eq!(expected, CcsdsTimeCodes::CucCcsdsEpoch); - assert_eq!(found, CcsdsTimeCodes::CucAgencyEpoch as u8); + assert_eq!(expected, CcsdsTimeCode::CucCcsdsEpoch); + assert_eq!(found, CcsdsTimeCode::CucAgencyEpoch as u8); } else { panic!("unexpected error: {}", err); } @@ -938,7 +1024,7 @@ mod tests { #[test] fn test_write_with_coarse_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120); + let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120); assert!(cuc.fractions.is_some()); assert_eq!(cuc.fractions.unwrap().1, 120); assert_eq!(cuc.fractions.unwrap().0, FractionalResolution::FourMs); @@ -957,10 +1043,10 @@ mod tests { #[test] fn test_read_with_coarse_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = TimeProviderCcsdsEpoch::new_with_coarse_fractions(0x30201060, 120); + let cuc = CucTime::new_with_coarse_fractions(0x30201060, 120); let res = cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - let res = TimeProviderCcsdsEpoch::from_bytes(&buf); + let res = CucTime::from_bytes(&buf); assert!(res.is_ok()); let read_back = res.unwrap(); assert_eq!(read_back, cuc); @@ -969,7 +1055,7 @@ mod tests { #[test] fn test_write_with_medium_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000); + let cuc = CucTime::new_with_medium_fractions(0x30303030, 30000); let res = cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); let written = res.unwrap(); @@ -981,10 +1067,10 @@ mod tests { #[test] fn test_read_with_medium_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = TimeProviderCcsdsEpoch::new_with_medium_fractions(0x30303030, 30000); + let cuc = CucTime::new_with_medium_fractions(0x30303030, 30000); let res = cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - let res = TimeProviderCcsdsEpoch::from_bytes(&buf); + let res = CucTime::from_bytes(&buf); assert!(res.is_ok()); let cuc_read_back = res.unwrap(); assert_eq!(cuc_read_back, cuc); @@ -993,8 +1079,7 @@ mod tests { #[test] fn test_write_with_fine_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = - TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); + let cuc = CucTime::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); assert!(cuc.is_ok()); let cuc = cuc.unwrap(); let res = cuc.write_to_bytes(&mut buf); @@ -1009,13 +1094,12 @@ mod tests { #[test] fn test_read_with_fine_fractions() { let mut buf: [u8; 16] = [0; 16]; - let cuc = - TimeProviderCcsdsEpoch::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); + let cuc = CucTime::new_with_fine_fractions(0x30303030, u16::MAX as u32 + 60000); assert!(cuc.is_ok()); let cuc = cuc.unwrap(); let res = cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - let res = TimeProviderCcsdsEpoch::from_bytes(&buf); + let res = CucTime::from_bytes(&buf); assert!(res.is_ok()); let cuc_read_back = res.unwrap(); assert_eq!(cuc_read_back, cuc); @@ -1089,7 +1173,7 @@ mod tests { #[test] fn update_fractions() { - let mut stamp = TimeProviderCcsdsEpoch::new(2000); + let mut stamp = CucTime::new(2000); let res = stamp.set_fractions(FractionalPart(FractionalResolution::SixtyNs, 5000)); assert!(res.is_ok()); assert!(stamp.fractions.is_some()); @@ -1100,13 +1184,13 @@ mod tests { #[test] fn set_fract_resolution() { - let mut stamp = TimeProviderCcsdsEpoch::new(2000); + let mut stamp = CucTime::new(2000); stamp.set_fractional_resolution(FractionalResolution::SixtyNs); assert!(stamp.fractions.is_some()); let fractions = stamp.fractions.unwrap(); assert_eq!(fractions.0, FractionalResolution::SixtyNs); assert_eq!(fractions.1, 0); - let res = stamp.update_from_now(); + let res = stamp.update_from_now(LEAP_SECONDS); assert!(res.is_ok()); } @@ -1120,7 +1204,12 @@ mod tests { assert_eq!(fractions.1, 2_u32.pow(3 * 8) - 2); } - fn check_stamp_after_addition(cuc_stamp: &TimeProviderCcsdsEpoch) { + fn check_stamp_after_addition(cuc_stamp: &CucTime) { + let cuc_with_leaps = cuc_stamp.to_leap_sec_helper(LEAP_SECONDS); + assert_eq!( + cuc_with_leaps.ccdsd_time_code(), + CcsdsTimeCode::CucCcsdsEpoch + ); assert_eq!(cuc_stamp.width_counter_pair().1, 202); let fractions = cuc_stamp.width_fractions_pair().unwrap().1; let expected_val = @@ -1130,12 +1219,12 @@ mod tests { // What I would roughly expect assert_eq!(cuc_stamp2.counter.1, 203); assert!(cuc_stamp2.fractions.unwrap().1 < 100); - assert!(cuc_stamp2.subsecond_millis() <= 1); + assert!(cuc_stamp2.subsec_millis() <= 1); } #[test] fn add_duration_basic() { - let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); + let mut cuc_stamp = CucTime::new(200); cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs); let duration = Duration::from_millis(2500); cuc_stamp += duration; @@ -1144,7 +1233,7 @@ mod tests { #[test] fn add_duration_basic_on_ref() { - let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); + let mut cuc_stamp = CucTime::new(200); cuc_stamp.set_fractional_resolution(FractionalResolution::FifteenUs); let duration = Duration::from_millis(2500); let new_stamp = cuc_stamp + duration; @@ -1153,7 +1242,7 @@ mod tests { #[test] fn add_duration_basic_no_fractions() { - let mut cuc_stamp = TimeProviderCcsdsEpoch::new(200); + let mut cuc_stamp = CucTime::new(200); let duration = Duration::from_millis(2000); cuc_stamp += duration; assert_eq!(cuc_stamp.counter(), 202); @@ -1162,7 +1251,7 @@ mod tests { #[test] fn add_duration_basic_on_ref_no_fractions() { - let cuc_stamp = TimeProviderCcsdsEpoch::new(200); + let cuc_stamp = CucTime::new(200); let duration = Duration::from_millis(2000); let new_stamp = cuc_stamp + duration; assert_eq!(new_stamp.counter(), 202); @@ -1170,8 +1259,7 @@ mod tests { } #[test] fn add_duration_overflow() { - let mut cuc_stamp = - TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 255), None).unwrap(); + let mut cuc_stamp = CucTime::new_generic(WidthCounterPair(1, 255), None).unwrap(); let duration = Duration::from_secs(10); cuc_stamp += duration; assert_eq!(cuc_stamp.counter.1, 10); @@ -1179,7 +1267,7 @@ mod tests { #[test] fn test_invalid_width_param() { - let error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(8, 0), None); + let error = CucTime::new_generic(WidthCounterPair(8, 0), None); assert!(error.is_err()); let error = error.unwrap_err(); if let CucError::InvalidCounterWidth(width) = error { @@ -1192,24 +1280,24 @@ mod tests { #[test] fn test_from_dt() { - let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); - let cuc = - TimeProviderCcsdsEpoch::from_date_time(&dt, FractionalResolution::Seconds).unwrap(); - assert_eq!(cuc.counter(), dt.timestamp() as u32); + let dt = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(); + let cuc = CucTime::from_chrono_date_time(&dt, FractionalResolution::Seconds, LEAP_SECONDS) + .unwrap(); + assert_eq!(cuc.counter(), dt.timestamp() as u32 + LEAP_SECONDS); } #[test] fn test_new_u16_width() { - let cuc = TimeProviderCcsdsEpoch::new_u16_counter(0); + let cuc = CucTime::new_u16_counter(0); assert_eq!(cuc.width(), 2); assert_eq!(cuc.counter(), 0); } #[test] fn from_unix_stamp() { - let unix_stamp = UnixTimestamp::new(0, 0).unwrap(); + let unix_stamp = UnixTime::new(0, 0); let cuc = - TimeProviderCcsdsEpoch::from_unix_stamp(&unix_stamp, FractionalResolution::Seconds) + CucTime::from_unix_stamp(&unix_stamp, FractionalResolution::Seconds, LEAP_SECONDS) .expect("failed to create cuc from unix stamp"); assert_eq!( cuc.counter(), @@ -1219,7 +1307,7 @@ mod tests { #[test] fn test_invalid_counter() { - let cuc_error = TimeProviderCcsdsEpoch::new_generic(WidthCounterPair(1, 256), None); + let cuc_error = CucTime::new_generic(WidthCounterPair(1, 256), None); assert!(cuc_error.is_err()); let cuc_error = cuc_error.unwrap_err(); if let CucError::InvalidCounter { width, counter } = cuc_error { @@ -1233,7 +1321,7 @@ mod tests { #[test] fn test_stamp_to_vec() { - let stamp = TimeProviderCcsdsEpoch::new_u16_counter(100); + let stamp = CucTime::new_u16_counter(100); let stamp_vec = stamp.to_vec().unwrap(); let mut buf: [u8; 16] = [0; 16]; stamp.write_to_bytes(&mut buf).unwrap(); diff --git a/src/time/mod.rs b/src/time/mod.rs index c231404..b9deea9 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -1,5 +1,6 @@ //! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) use crate::ByteConversionError; +#[cfg(feature = "chrono")] use chrono::{DateTime, LocalResult, TimeZone, Utc}; use core::cmp::Ordering; use core::fmt::{Display, Formatter}; @@ -13,6 +14,7 @@ use num_traits::float::FloatCore; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; + #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] @@ -27,10 +29,11 @@ pub mod cuc; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const SECONDS_PER_DAY: u32 = 86400; pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000; +pub const NANOS_PER_SECOND: u32 = 1_000_000_000; #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum CcsdsTimeCodes { +pub enum CcsdsTimeCode { CucCcsdsEpoch = 0b001, CucAgencyEpoch = 0b010, Cds = 0b100, @@ -38,16 +41,16 @@ pub enum CcsdsTimeCodes { AgencyDefined = 0b110, } -impl TryFrom for CcsdsTimeCodes { +impl TryFrom for CcsdsTimeCode { type Error = (); fn try_from(value: u8) -> Result { match value { - x if x == CcsdsTimeCodes::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCodes::CucCcsdsEpoch), - x if x == CcsdsTimeCodes::CucAgencyEpoch as u8 => Ok(CcsdsTimeCodes::CucAgencyEpoch), - x if x == CcsdsTimeCodes::Cds as u8 => Ok(CcsdsTimeCodes::Cds), - x if x == CcsdsTimeCodes::Ccs as u8 => Ok(CcsdsTimeCodes::Ccs), - x if x == CcsdsTimeCodes::AgencyDefined as u8 => Ok(CcsdsTimeCodes::AgencyDefined), + x if x == CcsdsTimeCode::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCode::CucCcsdsEpoch), + x if x == CcsdsTimeCode::CucAgencyEpoch as u8 => Ok(CcsdsTimeCode::CucAgencyEpoch), + x if x == CcsdsTimeCode::Cds as u8 => Ok(CcsdsTimeCode::Cds), + x if x == CcsdsTimeCode::Ccs as u8 => Ok(CcsdsTimeCode::Ccs), + x if x == CcsdsTimeCode::AgencyDefined as u8 => Ok(CcsdsTimeCode::AgencyDefined), _ => Err(()), } } @@ -55,20 +58,20 @@ impl TryFrom for CcsdsTimeCodes { /// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the /// value of the raw time code identification field is returned. -pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { +pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result { let raw_bits = (pfield >> 4) & 0b111; - CcsdsTimeCodes::try_from(raw_bits).map_err(|_| raw_bits) + CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits) } #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] pub enum TimestampError { - InvalidTimeCode { expected: CcsdsTimeCodes, found: u8 }, + InvalidTimeCode { expected: CcsdsTimeCode, found: u8 }, ByteConversion(ByteConversionError), Cds(cds::CdsError), Cuc(cuc::CucError), - DateBeforeCcsdsEpoch(DateTime), + DateBeforeCcsdsEpoch(UnixTime), CustomEpochNotSupported, } @@ -91,7 +94,7 @@ impl Display for TimestampError { write!(f, "time stamp: {e}") } TimestampError::DateBeforeCcsdsEpoch(e) => { - write!(f, "datetime with date before ccsds epoch: {e}") + write!(f, "datetime with date before ccsds epoch: {e:?}") } TimestampError::CustomEpochNotSupported => { write!(f, "custom epochs are not supported") @@ -204,16 +207,15 @@ pub trait TimeWriter { } } -pub trait TimeReader { - fn from_bytes(buf: &[u8]) -> Result - where - Self: Sized; +pub trait TimeReader: Sized { + fn from_bytes(buf: &[u8]) -> Result; } /// Trait for generic CCSDS time providers. /// -/// The UNIX helper methods and the [Self::date_time] method are not strictly necessary but extremely +/// The UNIX helper methods and the helper method are not strictly necessary but extremely /// practical because they are a very common and simple exchange format for time information. +/// Therefore, it was decided to keep them in this trait as well. pub trait CcsdsTimeProvider { fn len_as_bytes(&self) -> usize; @@ -222,56 +224,120 @@ pub trait CcsdsTimeProvider { /// entry denotes the length of the pfield and the second entry is the value of the pfield /// in big endian format. fn p_field(&self) -> (usize, [u8; 2]); - fn ccdsd_time_code(&self) -> CcsdsTimeCodes; + fn ccdsd_time_code(&self) -> CcsdsTimeCode; - fn unix_seconds(&self) -> i64; - fn subsecond_millis(&self) -> u16; - fn unix_stamp(&self) -> UnixTimestamp { - UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis()) + fn unix_secs(&self) -> i64; + fn subsec_nanos(&self) -> u32; + + fn subsec_millis(&self) -> u16 { + (self.subsec_nanos() / 1_000_000) as u16 } - fn date_time(&self) -> Option>; + fn unix_stamp(&self) -> UnixTime { + UnixTime::new(self.unix_secs(), self.subsec_nanos()) + } + + #[cfg(feature = "chrono")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] + fn chrono_date_time(&self) -> chrono::LocalResult> { + chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos()) + } + + #[cfg(feature = "timelib")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))] + fn timelib_date_time(&self) -> Result { + Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())? + + time::Duration::nanoseconds(self.subsec_nanos().into())) + } } -/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00. +/// UNIX time: Elapsed non-leap seconds since 1970-01-01T00:00:00+00:00 UTC. /// -/// Also can optionally include subsecond millisecond for greater accuracy. +/// This is a commonly used time format and can therefore also be used as a generic format to +/// convert other CCSDS time formats to and from. The subsecond precision is in nanoseconds +/// similarly to other common time formats and libraries. #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct UnixTimestamp { - pub unix_seconds: i64, - subsecond_millis: u16, +pub struct UnixTime { + secs: i64, + subsec_nanos: u32, } -impl UnixTimestamp { - /// Returns [None] if the subsecond millisecond value is larger than 999. - pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option { - if subsec_millis > 999 { +impl UnixTime { + /// The UNIX epoch time: 1970-01-01T00:00:00+00:00 UTC. + pub const EPOCH: Self = Self { + secs: 0, + subsec_nanos: 0, + }; + + /// The minimum possible `UnixTime`. + pub const MIN: Self = Self { + secs: i64::MIN, + subsec_nanos: 0, + }; + + /// The maximum possible `UnixTime`. + pub const MAX: Self = Self { + secs: i64::MAX, + subsec_nanos: NANOS_PER_SECOND - 1, + }; + + /// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a + /// second) + pub fn new_checked(unix_seconds: i64, subsec_nanos: u32) -> Option { + if subsec_nanos >= NANOS_PER_SECOND { return None; } - Some(Self::const_new(unix_seconds, subsec_millis)) + Some(Self::new(unix_seconds, subsec_nanos)) } - /// Like [Self::new] but const. Panics if the subsecond value is larger than 999. - pub const fn const_new(unix_seconds: i64, subsecond_millis: u16) -> Self { - if subsecond_millis > 999 { - panic!("subsec milliseconds exceeds 999"); + /// Returns [None] if the subsecond millisecond value is invalid (larger than fraction of a + /// second) + pub fn new_subsec_millis_checked(unix_seconds: i64, subsec_millis: u16) -> Option { + if subsec_millis >= 1000 { + return None; + } + Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000) + } + + /// This function will panic if the subsecond value is larger than the fraction of a second. + /// Use [new_checked] if you want to handle this case without a panic. + pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self { + if subsecond_nanos >= NANOS_PER_SECOND { + panic!("invalid subsecond nanos value"); } Self { - unix_seconds, - subsecond_millis, + secs: unix_seconds, + subsec_nanos: subsecond_nanos, } } - pub fn new_only_seconds(unix_seconds: i64) -> Self { + /// This function will panic if the subsecond value is larger than the fraction of a second. + /// Use [new_subsecond_millis_checked] if you want to handle this case without a panic. + pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self { + if subsecond_millis >= 1000 { + panic!("invalid subsecond millisecond value"); + } Self { - unix_seconds, - subsecond_millis: 0, + secs: unix_seconds, + subsec_nanos: subsecond_millis as u32 * 1_000_000, } } - pub fn subsecond_millis(&self) -> u16 { - self.subsecond_millis + pub fn new_only_secs(unix_seconds: i64) -> Self { + Self { + secs: unix_seconds, + subsec_nanos: 0, + } + } + + #[inline] + pub fn subsec_millis(&self) -> u16 { + (self.subsec_nanos / 1_000_000) as u16 + } + + pub fn subsec_nanos(&self) -> u32 { + self.subsec_nanos } #[cfg(feature = "std")] @@ -279,68 +345,78 @@ impl UnixTimestamp { pub fn from_now() -> Result { let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let epoch = now.as_secs(); - Ok(Self::const_new(epoch as i64, now.subsec_millis() as u16)) + Ok(Self::new(epoch as i64, now.subsec_nanos())) } #[inline] - pub fn unix_seconds_f64(&self) -> f64 { - self.unix_seconds as f64 + (self.subsecond_millis as f64 / 1000.0) + pub fn unix_secs_f64(&self) -> f64 { + self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0) } - pub fn as_date_time(&self) -> LocalResult> { - Utc.timestamp_opt( - self.unix_seconds, - self.subsecond_millis as u32 * 10_u32.pow(6), - ) + pub fn secs(&self) -> i64 { + self.secs + } + + #[cfg(feature = "chrono")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))] + pub fn chrono_date_time(&self) -> LocalResult> { + Utc.timestamp_opt(self.secs, self.subsec_nanos) + } + + #[cfg(feature = "timelib")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))] + pub fn timelib_date_time(&self) -> Result { + Ok(time::OffsetDateTime::from_unix_timestamp(self.secs())? + + time::Duration::nanoseconds(self.subsec_nanos().into())) } // Calculate the difference in milliseconds between two UnixTimestamps - pub fn difference_in_millis(&self, other: &UnixTimestamp) -> i64 { - let seconds_difference = self.unix_seconds - other.unix_seconds; + pub fn diff_in_millis(&self, other: &UnixTime) -> i64 { + let seconds_difference = self.secs - other.secs; // Convert seconds difference to milliseconds let milliseconds_difference = seconds_difference * 1000; // Calculate the difference in subsecond milliseconds directly - let subsecond_difference = self.subsecond_millis as i64 - other.subsecond_millis as i64; + let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64; // Combine the differences - milliseconds_difference + subsecond_difference + milliseconds_difference + (subsecond_difference_nanos / 1_000_000) } } -impl From> for UnixTimestamp { +impl From> for UnixTime { fn from(value: DateTime) -> Self { - Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16) + Self::new(value.timestamp(), value.timestamp_subsec_nanos()) } } -impl PartialOrd for UnixTimestamp { +impl PartialOrd for UnixTime { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for UnixTimestamp { +impl Ord for UnixTime { fn cmp(&self, other: &Self) -> Ordering { if self == other { return Ordering::Equal; } - match self.unix_seconds.cmp(&other.unix_seconds) { + match self.secs.cmp(&other.secs) { Ordering::Less => return Ordering::Less, Ordering::Greater => return Ordering::Greater, _ => (), } - match self.subsecond_millis().cmp(&other.subsecond_millis()) { + match self.subsec_millis().cmp(&other.subsec_millis()) { Ordering::Less => { - return if self.unix_seconds < 0 { + return if self.secs < 0 { Ordering::Greater } else { Ordering::Less } } Ordering::Greater => { - return if self.unix_seconds < 0 { + return if self.secs < 0 { Ordering::Less } else { Ordering::Greater @@ -360,11 +436,11 @@ pub struct StampDiff { pub duration_absolute: Duration, } -impl Sub for UnixTimestamp { +impl Sub for UnixTime { type Output = StampDiff; fn sub(self, rhs: Self) -> Self::Output { - let difference = self.difference_in_millis(&rhs); + let difference = self.diff_in_millis(&rhs); if difference < 0 { StampDiff { positive_duration: false, @@ -379,12 +455,9 @@ impl Sub for UnixTimestamp { } } -fn get_new_stamp_after_addition( - current_stamp: &UnixTimestamp, - duration: Duration, -) -> UnixTimestamp { - let mut new_subsec_millis = current_stamp.subsecond_millis() + duration.subsec_millis() as u16; - let mut new_unix_seconds = current_stamp.unix_seconds; +fn get_new_stamp_after_addition(current_stamp: &UnixTime, duration: Duration) -> UnixTime { + let mut new_subsec_nanos = current_stamp.subsec_nanos() + duration.subsec_nanos(); + let mut new_unix_seconds = current_stamp.secs; let mut increment_seconds = |value: u32| { if new_unix_seconds < 0 { new_unix_seconds = new_unix_seconds @@ -396,8 +469,8 @@ fn get_new_stamp_after_addition( .expect("new unix seconds would exceed i64::MAX"); } }; - if new_subsec_millis >= 1000 { - new_subsec_millis -= 1000; + if new_subsec_nanos >= 1_000_000_000 { + new_subsec_nanos -= 1_000_000_000; increment_seconds(1); } increment_seconds( @@ -406,7 +479,7 @@ fn get_new_stamp_after_addition( .try_into() .expect("duration seconds exceeds u32::MAX"), ); - UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis) + UnixTime::new(new_unix_seconds, new_subsec_nanos) } /// Please note that this operation will panic on the following conditions: @@ -414,7 +487,7 @@ fn get_new_stamp_after_addition( /// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN]. /// - Unix seconds after addition exceeds [i64::MAX]. /// - Seconds from duration to add exceeds [u32::MAX]. -impl AddAssign for UnixTimestamp { +impl AddAssign for UnixTime { fn add_assign(&mut self, duration: Duration) { *self = get_new_stamp_after_addition(self, duration); } @@ -425,7 +498,7 @@ impl AddAssign for UnixTimestamp { /// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN]. /// - Unix seconds after addition exceeds [i64::MAX]. /// - Unix seconds exceeds [u32::MAX]. -impl Add for UnixTimestamp { +impl Add for UnixTime { type Output = Self; fn add(self, duration: Duration) -> Self::Output { @@ -433,8 +506,8 @@ impl Add for UnixTimestamp { } } -impl Add for &UnixTimestamp { - type Output = UnixTimestamp; +impl Add for &UnixTime { + type Output = UnixTime; fn add(self, duration: Duration) -> Self::Output { get_new_stamp_after_addition(self, duration) @@ -445,10 +518,15 @@ impl Add for &UnixTimestamp { mod tests { use alloc::string::ToString; use chrono::{Datelike, Timelike}; - use std::format; + use std::{format, println}; use super::{cuc::CucError, *}; + #[allow(dead_code)] + const UNIX_STAMP_CONST: UnixTime = UnixTime::new(5, 999_999_999); + #[allow(dead_code)] + const UNIX_STAMP_CONST_2: UnixTime = UnixTime::new_subsec_millis(5, 999); + #[test] fn test_days_conversion() { assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0); @@ -484,28 +562,29 @@ mod tests { #[test] fn basic_unix_stamp_test() { - let stamp = UnixTimestamp::new_only_seconds(-200); - assert_eq!(stamp.unix_seconds, -200); - assert_eq!(stamp.subsecond_millis(), 0); - let stamp = UnixTimestamp::new_only_seconds(250); - assert_eq!(stamp.unix_seconds, 250); - assert_eq!(stamp.subsecond_millis(), 0); + let stamp = UnixTime::new_only_secs(-200); + assert_eq!(stamp.secs, -200); + assert_eq!(stamp.subsec_millis(), 0); + let stamp = UnixTime::new_only_secs(250); + assert_eq!(stamp.secs, 250); + assert_eq!(stamp.subsec_millis(), 0); } #[test] fn basic_float_unix_stamp_test() { - let stamp = UnixTimestamp::new(500, 600).unwrap(); - assert_eq!(stamp.unix_seconds, 500); - let subsec_millis = stamp.subsecond_millis(); + let stamp = UnixTime::new_subsec_millis_checked(500, 600).unwrap(); + assert_eq!(stamp.secs, 500); + let subsec_millis = stamp.subsec_millis(); assert_eq!(subsec_millis, 600); - assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001); + println!("{:?}", (500.6 - stamp.unix_secs_f64()).to_string()); + assert!((500.6 - stamp.unix_secs_f64()).abs() < 0.0001); } #[test] fn test_ord_larger() { - let stamp0 = UnixTimestamp::new_only_seconds(5); - let stamp1 = UnixTimestamp::new(5, 500).unwrap(); - let stamp2 = UnixTimestamp::new_only_seconds(6); + let stamp0 = UnixTime::new_only_secs(5); + let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap(); + let stamp2 = UnixTime::new_only_secs(6); assert!(stamp1 > stamp0); assert!(stamp2 > stamp0); assert!(stamp2 > stamp1); @@ -513,9 +592,9 @@ mod tests { #[test] fn test_ord_smaller() { - let stamp0 = UnixTimestamp::new_only_seconds(5); - let stamp1 = UnixTimestamp::new(5, 500).unwrap(); - let stamp2 = UnixTimestamp::new_only_seconds(6); + let stamp0 = UnixTime::new_only_secs(5); + let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap(); + let stamp2 = UnixTime::new_only_secs(6); assert!(stamp0 < stamp1); assert!(stamp0 < stamp2); assert!(stamp1 < stamp2); @@ -523,9 +602,9 @@ mod tests { #[test] fn test_ord_larger_neg_numbers() { - let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); - let stamp2 = UnixTimestamp::new_only_seconds(-6); + let stamp0 = UnixTime::new_only_secs(-5); + let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap(); + let stamp2 = UnixTime::new_only_secs(-6); assert!(stamp0 > stamp1); assert!(stamp0 > stamp2); assert!(stamp1 > stamp2); @@ -535,9 +614,9 @@ mod tests { #[test] fn test_ord_smaller_neg_numbers() { - let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); - let stamp2 = UnixTimestamp::new_only_seconds(-6); + let stamp0 = UnixTime::new_only_secs(-5); + let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap(); + let stamp2 = UnixTime::new_only_secs(-6); assert!(stamp2 < stamp1); assert!(stamp2 < stamp0); assert!(stamp1 < stamp0); @@ -548,8 +627,8 @@ mod tests { #[allow(clippy::nonminimal_bool)] #[test] fn test_eq() { - let stamp0 = UnixTimestamp::new(5, 0).unwrap(); - let stamp1 = UnixTimestamp::new_only_seconds(5); + let stamp0 = UnixTime::new(5, 0); + let stamp1 = UnixTime::new_only_secs(5); assert_eq!(stamp0, stamp1); assert!(stamp0 <= stamp1); assert!(stamp0 >= stamp1); @@ -559,27 +638,27 @@ mod tests { #[test] fn test_addition() { - let mut stamp0 = UnixTimestamp::new_only_seconds(1); + let mut stamp0 = UnixTime::new_only_secs(1); stamp0 += Duration::from_secs(5); - assert_eq!(stamp0.unix_seconds, 6); - assert_eq!(stamp0.subsecond_millis(), 0); + assert_eq!(stamp0.secs(), 6); + assert_eq!(stamp0.subsec_millis(), 0); let stamp1 = stamp0 + Duration::from_millis(500); - assert_eq!(stamp1.unix_seconds, 6); - assert_eq!(stamp1.subsecond_millis(), 500); + assert_eq!(stamp1.secs, 6); + assert_eq!(stamp1.subsec_millis(), 500); } #[test] fn test_addition_on_ref() { - let stamp0 = &UnixTimestamp::new(20, 500).unwrap(); + let stamp0 = &UnixTime::new_subsec_millis_checked(20, 500).unwrap(); let stamp1 = stamp0 + Duration::from_millis(2500); - assert_eq!(stamp1.unix_seconds, 23); - assert_eq!(stamp1.subsecond_millis(), 0); + assert_eq!(stamp1.secs, 23); + assert_eq!(stamp1.subsec_millis(), 0); } #[test] fn test_as_dt() { - let stamp = UnixTimestamp::new_only_seconds(0); - let dt = stamp.as_date_time().unwrap(); + let stamp = UnixTime::new_only_secs(0); + let dt = stamp.chrono_date_time().unwrap(); assert_eq!(dt.year(), 1970); assert_eq!(dt.month(), 1); assert_eq!(dt.day(), 1); @@ -590,26 +669,26 @@ mod tests { #[test] fn test_from_now() { - let stamp_now = UnixTimestamp::from_now().unwrap(); - let dt_now = stamp_now.as_date_time().unwrap(); + let stamp_now = UnixTime::from_now().unwrap(); + let dt_now = stamp_now.chrono_date_time().unwrap(); assert!(dt_now.year() >= 2020); } #[test] fn test_stamp_diff_positive_0() { - let stamp_later = UnixTimestamp::new(2, 0).unwrap(); + let stamp_later = UnixTime::new(2, 0); let StampDiff { positive_duration, duration_absolute, - } = stamp_later - UnixTimestamp::new(1, 0).unwrap(); + } = stamp_later - UnixTime::new(1, 0); assert!(positive_duration); assert_eq!(duration_absolute, Duration::from_secs(1)); } #[test] fn test_stamp_diff_positive_1() { - let stamp_later = UnixTimestamp::new(3, 800).unwrap(); - let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); + let stamp_later = UnixTime::new(3, 800 * 1_000_000); + let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap(); let StampDiff { positive_duration, duration_absolute, @@ -620,8 +699,8 @@ mod tests { #[test] fn test_stamp_diff_negative() { - let stamp_later = UnixTimestamp::new(3, 800).unwrap(); - let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); + let stamp_later = UnixTime::new_subsec_millis_checked(3, 800).unwrap(); + let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap(); let StampDiff { positive_duration, duration_absolute, @@ -632,13 +711,13 @@ mod tests { #[test] fn test_addition_spillover() { - let mut stamp0 = UnixTimestamp::new(1, 900).unwrap(); + let mut stamp0 = UnixTime::new_subsec_millis_checked(1, 900).unwrap(); stamp0 += Duration::from_millis(100); - assert_eq!(stamp0.unix_seconds, 2); - assert_eq!(stamp0.subsecond_millis(), 0); + assert_eq!(stamp0.secs, 2); + assert_eq!(stamp0.subsec_millis(), 0); stamp0 += Duration::from_millis(1100); - assert_eq!(stamp0.unix_seconds, 3); - assert_eq!(stamp0.subsecond_millis(), 100); + assert_eq!(stamp0.secs, 3); + assert_eq!(stamp0.subsec_millis(), 100); } #[test] @@ -647,4 +726,17 @@ mod tests { let stamp_error = TimestampError::from(cuc_error); assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}")); } + + #[test] + #[cfg(feature = "timelib")] + fn test_unix_stamp_as_timelib_datetime() { + let stamp_epoch = UnixTime::EPOCH; + let timelib_dt = stamp_epoch.timelib_date_time().unwrap(); + assert_eq!(timelib_dt.year(), 1970); + assert_eq!(timelib_dt.month(), time::Month::January); + assert_eq!(timelib_dt.day(), 1); + assert_eq!(timelib_dt.hour(), 0); + assert_eq!(timelib_dt.minute(), 0); + assert_eq!(timelib_dt.second(), 0); + } }