From 83c84bb31d1030c258b735a3341593820a2fe512 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 14 Mar 2024 10:50:31 +0100 Subject: [PATCH] make the API even more similar --- CHANGELOG.md | 21 ++-- src/time/cds.rs | 64 ++++++------- src/time/cuc.rs | 32 +++---- src/time/mod.rs | 249 +++++++++++++++++++++++++++--------------------- 4 files changed, 200 insertions(+), 166 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd96ae..9032268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ 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. +to check all the API changes in the **Changed** chapter. ## Fixed @@ -21,18 +21,23 @@ to check all the API changes. - `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 + feature gate. +- Added `CcsdsTimeProvider::timelib_date_time`. ## Changed -- `UnixTimestamp::new` renamed to `UnixTimestamp::new_checked`. -- `UnixTimestamp` now has a nanosecond subsecond precision. The `new` constructor now expects +- Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time`. +- `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 `UnixTimestamp::new_subsec_millis` and `UnixTimestamp::new_subsec_millis_checked` API +- 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 `subsecond_nanos` method in addition to a default - implementation for the `subsecond_millis` method. -- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time` +- `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`. # [v0.11.0-rc.0] 2024-03-04 diff --git a/src/time/cds.rs b/src/time/cds.rs index f83a908..fc553ee 100644 --- a/src/time/cds.rs +++ b/src/time/cds.rs @@ -35,7 +35,7 @@ use serde::{Serialize, Deserialize}; use super::{ ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCodes, CcsdsTimeProvider, TimeReader, - TimeWriter, TimestampError, UnixTimestamp, MS_PER_DAY, SECONDS_PER_DAY, + 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. @@ -188,7 +188,7 @@ pub struct TimeProvider { 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. @@ -231,7 +231,7 @@ impl ConversionFromUnix { let ccsds_days = unix_to_ccsds_days(unix_days); if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 0) || ccsds_days < 0 { return Err(TimestampError::DateBeforeCcsdsEpoch( - UnixTimestamp::new_checked(unix_seconds, subsec_nanos) + UnixTime::new_checked(unix_seconds, subsec_nanos) .expect("unix timestamp creation failed"), )); } @@ -341,7 +341,7 @@ impl ConversionFromChronoDatetime { ) -> Result { // The CDS timestamp does not support timestamps before the CCSDS epoch. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from( + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from( *dt, ))); } @@ -643,7 +643,7 @@ impl TimeProvider { if let Some(precision) = self.precision_as_ns() { subsec_nanos += precision; } - self.unix_stamp = UnixTimestamp::new(unix_days_seconds, subsec_nanos); + self.unix_stamp = UnixTime::new(unix_days_seconds, subsec_nanos); } fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { @@ -706,13 +706,13 @@ impl TimeProvider { } 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_nanos, + unix_stamp.secs, + unix_stamp.subsec_nanos, submillis_prec, )?; Self::generic_from_conversion(days_len, conv_from_dt) @@ -844,7 +844,7 @@ impl TimeProvider { /// [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_stamp_with_u24_days( - unix_stamp: &UnixTimestamp, + unix_stamp: &UnixTime, submillis_prec: SubmillisPrecision, ) -> Result { Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Long24Bits, submillis_prec) @@ -941,7 +941,7 @@ impl TimeProvider { /// [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_stamp_with_u16_days( - unix_stamp: &UnixTimestamp, + unix_stamp: &UnixTime, submillis_prec: SubmillisPrecision, ) -> Result { Self::from_unix_generic(unix_stamp, LengthOfDaySegment::Short16Bits, submillis_prec) @@ -1205,16 +1205,16 @@ impl CcsdsTimeProvider for TimeProvider i64 { - self.unix_stamp.unix_seconds + fn unix_secs(&self) -> i64 { + self.unix_stamp.secs } #[inline] - fn subsecond_nanos(&self) -> u32 { - self.unix_stamp.subsecond_nanos + fn subsec_nanos(&self) -> u32 { + self.unix_stamp.subsec_nanos } #[inline] - fn unix_stamp(&self) -> UnixTimestamp { + fn unix_stamp(&self) -> UnixTime { self.unix_stamp } } @@ -1351,7 +1351,7 @@ impl TryFrom> for TimeProvider { mod tests { use super::*; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; - use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, MS_PER_DAY}; + 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}; @@ -1364,16 +1364,16 @@ mod tests { let time_stamper = TimeProvider::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_nanos; + 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_nanos(), 0); + assert_eq!(time_stamper.subsec_nanos(), 0); assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds); assert_eq!( time_stamper.p_field(), @@ -1391,7 +1391,7 @@ 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); + assert_eq!(time_stamper.unix_stamp().secs, 0); assert_eq!( time_stamper.submillis_precision(), SubmillisPrecision::Absent @@ -1404,11 +1404,11 @@ mod tests { 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_nanos(), 40 * 10_u32.pow(6)); - assert_eq!(time_stamper.subsecond_millis(), 40); + assert_eq!(time_stamper.subsec_nanos(), 40 * 10_u32.pow(6)); + assert_eq!(time_stamper.subsec_millis(), 40); let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); - assert_eq!(time_stamper.subsecond_nanos(), 40 * 10_u32.pow(6)); - assert_eq!(time_stamper.subsecond_millis(), 40); + assert_eq!(time_stamper.subsec_nanos(), 40 * 10_u32.pow(6)); + assert_eq!(time_stamper.subsec_millis(), 40); } #[test] @@ -1470,7 +1470,7 @@ mod tests { let time_stamper_0 = TimeProvider::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); @@ -1932,7 +1932,7 @@ mod tests { let unix_secs = 0; let subsec_millis = 0; let time_provider = TimeProvider::from_unix_stamp_with_u16_days( - &UnixTimestamp::new(unix_secs, subsec_millis), + &UnixTime::new(unix_secs, subsec_millis), SubmillisPrecision::Absent, ) .expect("creating provider from unix stamp failed"); @@ -1944,7 +1944,7 @@ mod tests { let unix_secs = 0; let subsec_millis = 0; let time_provider = TimeProvider::from_unix_stamp_with_u24_days( - &UnixTimestamp::new(unix_secs, subsec_millis), + &UnixTime::new(unix_secs, subsec_millis), SubmillisPrecision::Absent, ) .expect("creating provider from unix stamp failed"); @@ -1981,7 +1981,7 @@ mod tests { let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let subsec_millis = 0; let time_provider = TimeProvider::from_unix_stamp_with_u16_days( - &UnixTimestamp::new(unix_secs, subsec_millis), + &UnixTime::new(unix_secs, subsec_millis), SubmillisPrecision::Absent, ) .expect("creating provider from unix stamp failed"); @@ -1993,7 +1993,7 @@ mod tests { let invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64; let subsec_millis = 0; match TimeProvider::from_unix_stamp_with_u16_days( - &UnixTimestamp::new(invalid_unix_secs, subsec_millis), + &UnixTime::new(invalid_unix_secs, subsec_millis), SubmillisPrecision::Absent, ) { Ok(_) => { @@ -2020,7 +2020,7 @@ mod tests { let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let subsec_millis = 0; match TimeProvider::from_unix_stamp_with_u16_days( - &UnixTimestamp::new(unix_secs as i64, subsec_millis), + &UnixTime::new(unix_secs as i64, subsec_millis), SubmillisPrecision::Absent, ) { Ok(_) => { @@ -2028,7 +2028,7 @@ mod tests { } Err(e) => { if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { - let dt = dt.as_date_time(); + let dt = dt.chrono_date_time(); if let chrono::LocalResult::Single(dt) = dt { assert_eq!(dt.year(), 1957); assert_eq!(dt.month(), 12); @@ -2304,7 +2304,7 @@ mod tests { fn test_update_from_now() { let mut stamp = TimeProvider::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); } diff --git a/src/time/cuc.rs b/src/time/cuc.rs index db855e5..499e598 100644 --- a/src/time/cuc.rs +++ b/src/time/cuc.rs @@ -12,9 +12,12 @@ use core::u64; use crate::ByteConversionError; -use super::{CcsdsTimeCodes, TimestampError, unix_epoch_to_ccsds_epoch, UnixTimestamp, ccsds_epoch_to_unix_epoch, TimeReader, TimeWriter, CcsdsTimeProvider}; #[cfg(feature = "std")] use super::StdTimestampError; +use super::{ + ccsds_epoch_to_unix_epoch, unix_epoch_to_ccsds_epoch, CcsdsTimeCodes, CcsdsTimeProvider, + TimeReader, TimeWriter, TimestampError, UnixTime, +}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] @@ -315,7 +318,6 @@ impl TimeProviderCcsdsEpoch { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn update_from_now(&mut self) -> 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 @@ -340,9 +342,7 @@ impl TimeProviderCcsdsEpoch { ) -> Result { // Year before CCSDS epoch is invalid. if dt.year() < 1958 { - return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from( - *dt, - ))); + return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTime::from(*dt))); } let counter = dt .timestamp() @@ -359,11 +359,11 @@ 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)); @@ -371,10 +371,8 @@ impl TimeProviderCcsdsEpoch { ccsds_epoch .checked_add(i64::from(leap_seconds)) .ok_or(TimestampError::Cuc(CucError::LeapSecondCorrectionError))?; - let fractions = fractional_part_from_subsec_ns( - res, - unix_stamp.subsecond_millis() as u64 * 10_u64.pow(6), - ); + 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, @@ -693,11 +691,11 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch { CcsdsTimeCodes::CucCcsdsEpoch } - fn unix_seconds(&self) -> i64 { + fn unix_secs(&self) -> i64 { self.unix_seconds() } - fn subsecond_nanos(&self) -> u32 { + fn subsec_nanos(&self) -> u32 { if let Some(fractions) = self.fractions { if fractions.0 == FractionalResolution::Seconds { return 0; @@ -806,7 +804,7 @@ impl Add for &TimeProviderCcsdsEpoch { #[cfg(test)] mod tests { - use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY}; + use crate::time::{UnixTime, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY}; use super::*; use alloc::string::ToString; @@ -852,7 +850,7 @@ mod tests { let zero_cuc = zero_cuc.unwrap(); let res = zero_cuc.write_to_bytes(&mut buf); assert!(res.is_ok()); - assert_eq!(zero_cuc.subsecond_nanos(), 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(); @@ -1245,7 +1243,7 @@ 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] @@ -1327,7 +1325,7 @@ mod tests { #[test] fn from_unix_stamp() { - let unix_stamp = UnixTimestamp::new(0, 0); + let unix_stamp = UnixTime::new(0, 0); let cuc = TimeProviderCcsdsEpoch::from_unix_stamp( &unix_stamp, FractionalResolution::Seconds, diff --git a/src/time/mod.rs b/src/time/mod.rs index 004772d..d73ada0 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -29,6 +29,7 @@ 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))] @@ -70,7 +71,7 @@ pub enum TimestampError { ByteConversion(ByteConversionError), Cds(cds::CdsError), Cuc(cuc::CucError), - DateBeforeCcsdsEpoch(UnixTimestamp), + DateBeforeCcsdsEpoch(UnixTime), CustomEpochNotSupported, } @@ -232,21 +233,21 @@ pub trait CcsdsTimeProvider { fn p_field(&self) -> (usize, [u8; 2]); fn ccdsd_time_code(&self) -> CcsdsTimeCodes; - fn unix_seconds(&self) -> i64; - fn subsecond_nanos(&self) -> u32; + fn unix_secs(&self) -> i64; + fn subsec_nanos(&self) -> u32; - fn subsecond_millis(&self) -> u16 { - (self.subsecond_nanos() / 1_000_000) as u16 + fn subsec_millis(&self) -> u16 { + (self.subsec_nanos() / 1_000_000) as u16 } - fn unix_stamp(&self) -> UnixTimestamp { - UnixTimestamp::new(self.unix_seconds(), self.subsecond_nanos()) + 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_seconds(), self.subsecond_nanos()) + chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos()) } #[cfg(feature = "timelib")] @@ -259,22 +260,41 @@ pub trait CcsdsTimeProvider { } } -/// 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. -/// It is assumed that leap second correction was already applied and the reference time is UTC. +/// 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_nanos: u32, +pub struct UnixTime { + secs: i64, + subsec_nanos: u32, } -impl UnixTimestamp { +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 > 1_000_000_000 - 1 { + if subsec_nanos >= NANOS_PER_SECOND { return None; } Some(Self::new(unix_seconds, subsec_nanos)) @@ -283,7 +303,7 @@ impl UnixTimestamp { /// 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 > 999 { + if subsec_millis >= 1000 { return None; } Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000) @@ -292,42 +312,41 @@ impl UnixTimestamp { /// 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 { - // This could actually be used to model leap seconds, but we will ignore that for now. - if subsecond_nanos > 1_000_000_000 - 1 { + if subsecond_nanos >= NANOS_PER_SECOND { panic!("invalid subsecond nanos value"); } Self { - unix_seconds, - subsecond_nanos, + secs: unix_seconds, + subsec_nanos: subsecond_nanos, } } /// 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 { - // This could actually be used to model leap seconds, but we will ignore that for now. - if subsecond_millis > 999 { + if subsecond_millis >= 1000 { panic!("invalid subsecond millisecond value"); } Self { - unix_seconds, - subsecond_nanos: subsecond_millis as u32 * 1_000_000, + secs: unix_seconds, + subsec_nanos: subsecond_millis as u32 * 1_000_000, } } - pub fn new_only_seconds(unix_seconds: i64) -> Self { + pub fn new_only_secs(unix_seconds: i64) -> Self { Self { - unix_seconds, - subsecond_nanos: 0, + secs: unix_seconds, + subsec_nanos: 0, } } - pub fn subsecond_millis(&self) -> u16 { - (self.subsecond_nanos / 1_000_000) as u16 + #[inline] + pub fn subsec_millis(&self) -> u16 { + (self.subsec_nanos / 1_000_000) as u16 } - pub fn subsecond_nanos(&self) -> u32 { - self.subsecond_nanos + pub fn subsec_nanos(&self) -> u32 { + self.subsec_nanos } #[cfg(feature = "std")] @@ -339,61 +358,76 @@ impl UnixTimestamp { } #[inline] - pub fn unix_seconds_f64(&self) -> f64 { - self.unix_seconds as f64 + (self.subsecond_nanos as f64 / 1_000_000_000.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_nanos) + 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")))] + fn timelib_date_time(&self) -> Result { + Ok( + time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())? + + time::Duration::nanoseconds(self.subsecond_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_nanos = self.subsecond_nanos as i64 - other.subsecond_nanos as i64; + let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64; // Combine the differences milliseconds_difference + (subsecond_difference_nanos / 1_000_000) } } -impl From> for UnixTimestamp { +impl From> for UnixTime { fn from(value: DateTime) -> Self { 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 @@ -413,11 +447,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, @@ -432,12 +466,9 @@ impl Sub for UnixTimestamp { } } -fn get_new_stamp_after_addition( - current_stamp: &UnixTimestamp, - duration: Duration, -) -> UnixTimestamp { - let mut new_subsec_nanos = current_stamp.subsecond_nanos() + duration.subsec_nanos(); - 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 @@ -459,7 +490,7 @@ fn get_new_stamp_after_addition( .try_into() .expect("duration seconds exceeds u32::MAX"), ); - UnixTimestamp::new(new_unix_seconds, new_subsec_nanos) + UnixTime::new(new_unix_seconds, new_subsec_nanos) } /// Please note that this operation will panic on the following conditions: @@ -467,7 +498,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); } @@ -478,7 +509,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 { @@ -486,8 +517,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) @@ -503,9 +534,9 @@ mod tests { use super::{cuc::CucError, *}; #[allow(dead_code)] - const UNIX_STAMP_CONST: UnixTimestamp = UnixTimestamp::new(5, 999_999_999); + const UNIX_STAMP_CONST: UnixTime = UnixTime::new(5, 999_999_999); #[allow(dead_code)] - const UNIX_STAMP_CONST_2: UnixTimestamp = UnixTimestamp::new_subsec_millis(5, 999); + const UNIX_STAMP_CONST_2: UnixTime = UnixTime::new_subsec_millis(5, 999); #[test] fn test_days_conversion() { @@ -542,29 +573,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_subsec_millis_checked(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); - println!("{:?}", (500.6 - stamp.unix_seconds_f64()).to_string()); - 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_subsec_millis_checked(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); @@ -572,9 +603,9 @@ mod tests { #[test] fn test_ord_smaller() { - let stamp0 = UnixTimestamp::new_only_seconds(5); - let stamp1 = UnixTimestamp::new_subsec_millis_checked(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); @@ -582,9 +613,9 @@ mod tests { #[test] fn test_ord_larger_neg_numbers() { - let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new_subsec_millis_checked(-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); @@ -594,9 +625,9 @@ mod tests { #[test] fn test_ord_smaller_neg_numbers() { - let stamp0 = UnixTimestamp::new_only_seconds(-5); - let stamp1 = UnixTimestamp::new_subsec_millis_checked(-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); @@ -607,8 +638,8 @@ mod tests { #[allow(clippy::nonminimal_bool)] #[test] fn test_eq() { - let stamp0 = UnixTimestamp::new(5, 0); - 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); @@ -618,27 +649,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_subsec_millis_checked(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); @@ -649,26 +680,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); + let stamp_later = UnixTime::new(2, 0); let StampDiff { positive_duration, duration_absolute, - } = stamp_later - UnixTimestamp::new(1, 0); + } = 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 * 1_000_000); - let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(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, @@ -679,8 +710,8 @@ mod tests { #[test] fn test_stamp_diff_negative() { - let stamp_later = UnixTimestamp::new_subsec_millis_checked(3, 800).unwrap(); - let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(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, @@ -691,13 +722,13 @@ mod tests { #[test] fn test_addition_spillover() { - let mut stamp0 = UnixTimestamp::new_subsec_millis_checked(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]