UnixTimestamp now has nanosecond precision
Some checks failed
Rust/spacepackets/pipeline/head There was a failure building this commit

This commit is contained in:
Robin Müller 2024-03-14 10:13:29 +01:00
parent aae246dca6
commit 05b6503851
Signed by: muellerr
GPG Key ID: A649FB78196E3849
5 changed files with 349 additions and 263 deletions

View File

@ -8,6 +8,9 @@ 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.
## Fixed
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI
@ -20,6 +23,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
feature gate
## Changed
- `UnixTimestamp::new` renamed to `UnixTimestamp::new_checked`.
- `UnixTimestamp` 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
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`
# [v0.11.0-rc.0] 2024-03-04
## Added

View File

@ -48,8 +48,9 @@ optional = true
version = "0.2"
default-features = false
[dev-dependencies.postcard]
version = "1"
[dev-dependencies]
postcard = "1"
chrono = "0.4"
[features]
default = ["std"]

View File

@ -4,20 +4,40 @@
//! 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;
#[cfg(feature = "chrono")]
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::{Serialize, Deserialize};
use super::{
ccsds_to_unix_days, unix_to_ccsds_days, CcsdsTimeCodes, CcsdsTimeProvider, TimeReader,
TimeWriter, TimestampError, UnixTimestamp, 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 MIN_CDS_FIELD_LEN: usize = 7;
@ -165,7 +185,6 @@ pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
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<Utc>`].
@ -195,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<Self, TimestampError> {
fn new(
unix_seconds: i64,
subsec_nanos: u32,
precision: SubmillisPrecision,
) -> Result<Self, TimestampError> {
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(),
UnixTimestamp::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 {
@ -236,7 +266,7 @@ impl CdsCommon for ConversionFromUnix {
}
fn submillis(&self) -> u32 {
0
self.submillis
}
}
@ -246,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
}
@ -269,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; }}
}
@ -286,27 +316,38 @@ 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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
impl ConversionFromChronoDatetime {
fn new(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Absent)
}
fn new_with_submillis_us_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
fn new_with_submillis_us_prec(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Microseconds)
}
fn new_with_submillis_ps_prec(dt: &DateTime<Utc>) -> Result<Self, TimestampError> {
fn new_with_submillis_ps_prec(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Picoseconds)
}
fn new_generic(dt: &DateTime<Utc>, prec: SubmillisPrecision) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
fn new_generic(
dt: &chrono::DateTime<chrono::Utc>,
prec: SubmillisPrecision,
) -> Result<Self, TimestampError> {
// The CDS timestamp does not support timestamps before the CCSDS epoch.
if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::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 => {
@ -351,7 +392,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 {
@ -589,20 +631,6 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
self.calc_unix_seconds(unix_days_seconds, ms_of_day);
}
fn calc_ns_since_last_second(&self) -> u32 {
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;
}
_ => (),
}
ns_since_last_sec
}
#[inline]
fn calc_unix_seconds(&mut self, mut unix_days_seconds: i64, ms_of_day: u32) {
let seconds_of_day = (ms_of_day / 1000) as i64;
@ -611,35 +639,11 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
} else {
unix_days_seconds += seconds_of_day;
}
self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16);
}
#[cfg(feature = "chrono")]
fn calc_chrono_date_time(
&self,
ns_since_last_second: u32,
) -> Option<chrono::DateTime<chrono::Utc>> {
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
}
#[cfg(feature = "timelib")]
fn calc_timelib_date_time(
&self,
ns_since_last_second: u32,
) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(
time::OffsetDateTime::from_unix_timestamp(self.unix_stamp.unix_seconds)?
+ time::Duration::nanoseconds(ns_since_last_second.into()),
)
self.unix_stamp = UnixTimestamp::new(unix_days_seconds, subsec_nanos);
}
fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> {
@ -674,36 +678,43 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
Ok(provider)
}
#[cfg(feature = "chrono")]
fn from_dt_generic(
dt: &DateTime<Utc>,
dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> {
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<Utc>,
dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> {
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<Utc>,
dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> {
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,
days_len: LengthOfDaySegment,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> {
let conv_from_dt =
ConversionFromUnix::new(unix_stamp.unix_seconds, unix_stamp.subsecond_millis as u32)?;
let conv_from_dt = ConversionFromUnix::new(
unix_stamp.unix_seconds,
unix_stamp.subsecond_nanos,
submillis_prec,
)?;
Self::generic_from_conversion(days_len, conv_from_dt)
}
@ -818,7 +829,10 @@ impl TimeProvider<DaysLen24Bits> {
/// 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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
}
@ -829,19 +843,26 @@ impl TimeProvider<DaysLen24Bits> {
/// 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(
pub fn from_unix_stamp_with_u24_days(
unix_stamp: &UnixTimestamp,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> {
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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days_us_precision(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u24_days_ps_precision(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Long24Bits)
}
@ -898,7 +919,10 @@ impl TimeProvider<DaysLen16Bits> {
/// 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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits)
}
@ -916,19 +940,26 @@ impl TimeProvider<DaysLen16Bits> {
/// 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(
pub fn from_unix_stamp_with_u16_days(
unix_stamp: &UnixTimestamp,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> {
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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days_us_precision(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
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<Utc>) -> Result<Self, TimestampError> {
#[cfg(feature = "chrono")]
pub fn from_dt_with_u16_days_ps_precision(
dt: &chrono::DateTime<chrono::Utc>,
) -> Result<Self, TimestampError> {
Self::from_dt_generic_ps_prec(dt, LengthOfDaySegment::Short16Bits)
}
@ -1140,20 +1171,22 @@ impl AddAssign<Duration> for TimeProvider<DaysLen24Bits> {
}
}
impl TryFrom<DateTime<Utc>> for TimeProvider<DaysLen16Bits> {
#[cfg(feature = "chrono")]
impl TryFrom<chrono::DateTime<chrono::Utc>> for TimeProvider<DaysLen16Bits> {
type Error = TimestampError;
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromDatetime::new(&dt)?;
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromChronoDatetime::new(&dt)?;
Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion)
}
}
impl TryFrom<DateTime<Utc>> for TimeProvider<DaysLen24Bits> {
#[cfg(feature = "chrono")]
impl TryFrom<chrono::DateTime<chrono::Utc>> for TimeProvider<DaysLen24Bits> {
type Error = TimestampError;
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromDatetime::new(&dt)?;
fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromChronoDatetime::new(&dt)?;
Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion)
}
}
@ -1176,26 +1209,14 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
self.unix_stamp.unix_seconds
}
#[inline]
fn subsecond_millis(&self) -> u16 {
self.unix_stamp.subsecond_millis
fn subsecond_nanos(&self) -> u32 {
self.unix_stamp.subsecond_nanos
}
#[inline]
fn unix_stamp(&self) -> UnixTimestamp {
self.unix_stamp
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
self.calc_chrono_date_time(self.calc_ns_since_last_second())
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
self.calc_timelib_date_time(self.calc_ns_since_last_second())
}
}
impl TimeReader for TimeProvider<DaysLen16Bits> {
@ -1330,6 +1351,7 @@ impl TryFrom<TimeProvider<DaysLen24Bits>> for TimeProvider<DaysLen16Bits> {
mod tests {
use super::*;
use crate::time::TimestampError::{ByteConversion, InvalidTimeCode};
use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, MS_PER_DAY};
use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall};
use alloc::string::ToString;
use chrono::{Datelike, NaiveDate, Timelike};
@ -1345,13 +1367,13 @@ mod tests {
unix_stamp.unix_seconds,
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64
);
let subsecond_millis = unix_stamp.subsecond_millis;
let subsecond_millis = unix_stamp.subsecond_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.subsecond_nanos(), 0);
assert_eq!(time_stamper.ccdsd_time_code(), CcsdsTimeCodes::Cds);
assert_eq!(
time_stamper.p_field(),
@ -1382,8 +1404,10 @@ 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);
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);
}
@ -1565,7 +1589,7 @@ mod tests {
fn generic_now_test<T: ProvidesDaysLength>(
timestamp_now: TimeProvider<T>,
compare_stamp: DateTime<Utc>,
compare_stamp: chrono::DateTime<chrono::Utc>,
) {
let dt = timestamp_now.chrono_date_time().unwrap();
if compare_stamp.year() > dt.year() {
@ -1591,35 +1615,35 @@ mod tests {
#[test]
fn test_time_now() {
let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap();
let compare_stamp = Utc::now();
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 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 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 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 compare_stamp = chrono::Utc::now();
generic_now_test(timestamp_now, compare_stamp);
}
@ -1743,19 +1767,19 @@ mod tests {
assert_eq!(stamp_deserialized.submillis(), 5e8 as u32);
}
fn generic_dt_case_0_no_prec(subsec_millis: u32) -> DateTime<Utc> {
fn generic_dt_case_0_no_prec(subsec_millis: u32) -> chrono::DateTime<chrono::Utc> {
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<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
datetime_utc: DateTime<Utc>,
datetime_utc: chrono::DateTime<chrono::Utc>,
) {
// 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,21 +1814,21 @@ mod tests {
assert_eq!(time_provider, time_provider_2);
}
fn generic_dt_case_1_us_prec(subsec_millis: u32) -> DateTime<Utc> {
fn generic_dt_case_1_us_prec(subsec_millis: u32) -> chrono::DateTime<chrono::Utc> {
// 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<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
datetime_utc: DateTime<Utc>,
datetime_utc: chrono::DateTime<chrono::Utc>,
) {
// 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.
@ -1839,7 +1863,7 @@ mod tests {
generic_check_dt_case_1_us_prec(&time_provider, subsec_millis, datetime_utc);
}
fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (DateTime<Utc>, u32) {
fn generic_dt_case_2_ps_prec(subsec_millis: u32) -> (chrono::DateTime<chrono::Utc>, u32) {
// 250 ms + 500 us
let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000;
let submilli_nanos = subsec_nanos % 10_u32.pow(6);
@ -1848,7 +1872,7 @@ 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,
)
@ -1858,7 +1882,7 @@ mod tests {
time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32,
submilli_nanos: u32,
datetime_utc: DateTime<Utc>,
datetime_utc: chrono::DateTime<chrono::Utc>,
) {
// 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.
@ -1907,10 +1931,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 = TimeProvider::from_unix_stamp_with_u16_days(
&UnixTimestamp::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)
}
@ -1919,10 +1943,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 = TimeProvider::from_unix_stamp_with_u24_days(
&UnixTimestamp::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)
}
@ -1934,10 +1958,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 = TimeProvider::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);
@ -1953,10 +1980,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 = TimeProvider::from_unix_stamp_with_u16_days(
&UnixTimestamp::new(unix_secs, subsec_millis),
SubmillisPrecision::Absent,
)
.expect("creating provider from unix stamp failed");
assert_eq!(time_provider.ccsds_days, 0)
}
@ -1965,10 +1992,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 TimeProvider::from_unix_stamp_with_u16_days(
&UnixTimestamp::new(invalid_unix_secs, subsec_millis),
SubmillisPrecision::Absent,
) {
Ok(_) => {
panic!("creation should not succeed")
}
@ -1992,21 +2019,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 TimeProvider::from_unix_stamp_with_u16_days(
&UnixTimestamp::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.as_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)
}
@ -2222,12 +2254,12 @@ 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);
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());
}
}

View File

@ -2,15 +2,29 @@
//! [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::*;
#[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;
use super::{CcsdsTimeCodes, TimestampError, unix_epoch_to_ccsds_epoch, UnixTimestamp, ccsds_epoch_to_unix_epoch, TimeReader, TimeWriter, CcsdsTimeProvider};
#[cfg(feature = "std")]
use super::StdTimestampError;
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[cfg(feature = "chrono")]
use chrono::Datelike;
use core::fmt::Debug;
use core::ops::{Add, AddAssign};
use core::time::Duration;
use core::u64;
#[cfg(feature = "alloc")]
use super::ccsds_time_code_from_p_field;
const MIN_CUC_LEN: usize = 2;
@ -301,6 +315,7 @@ 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
@ -325,7 +340,9 @@ impl TimeProviderCcsdsEpoch {
) -> Result<Self, TimestampError> {
// Year before CCSDS epoch is invalid.
if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt));
return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from(
*dt,
)));
}
let counter = dt
.timestamp()
@ -349,9 +366,7 @@ impl TimeProviderCcsdsEpoch {
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
// 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));
}
ccsds_epoch
.checked_add(i64::from(leap_seconds))
@ -682,43 +697,16 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
self.unix_seconds()
}
fn subsecond_millis(&self) -> u16 {
fn subsecond_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) / 10_u32.pow(6) as u64) as u16;
return convert_fractional_part_to_ns(fractions) as u32;
}
0
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
let mut ns = 0;
if let Some(fractional_part) = self.fractions {
ns = convert_fractional_part_to_ns(fractional_part);
}
if let LocalResult::Single(res) = chrono::Utc.timestamp_opt(self.unix_seconds(), ns as u32)
{
return Some(res);
}
None
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
let mut ns = 0;
if let Some(fractional_part) = self.fractions {
ns = convert_fractional_part_to_ns(fractional_part);
}
Ok(
time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())?
+ time::Duration::nanoseconds(ns as i64),
)
}
}
fn get_provider_values_after_duration_addition(
@ -818,9 +806,11 @@ impl Add<Duration> for &TimeProviderCcsdsEpoch {
#[cfg(test)]
mod tests {
use crate::time::{UnixTimestamp, 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;
@ -840,14 +830,14 @@ mod tests {
let fractions = zero_cuc.width_fractions_pair();
assert!(fractions.is_none());
let dt = zero_cuc.chrono_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);
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]
@ -862,7 +852,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_millis(), 0);
assert_eq!(zero_cuc.subsecond_nanos(), 0);
assert_eq!(zero_cuc.len_as_bytes(), 5);
assert_eq!(pfield_len(buf[0]), 1);
let written = res.unwrap();
@ -880,17 +870,19 @@ mod tests {
#[test]
fn test_datetime_now() {
let now = Utc::now();
let now = chrono::Utc::now();
let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS);
assert!(cuc_now.is_ok());
let cuc_now = cuc_now.unwrap();
let dt_opt = cuc_now.chrono_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);
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]
@ -1316,7 +1308,7 @@ mod tests {
#[test]
fn test_from_dt() {
let dt = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let dt = chrono::Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let cuc = TimeProviderCcsdsEpoch::from_chrono_date_time(
&dt,
FractionalResolution::Seconds,
@ -1335,7 +1327,7 @@ mod tests {
#[test]
fn from_unix_stamp() {
let unix_stamp = UnixTimestamp::new(0, 0).unwrap();
let unix_stamp = UnixTimestamp::new(0, 0);
let cuc = TimeProviderCcsdsEpoch::from_unix_stamp(
&unix_stamp,
FractionalResolution::Seconds,

View File

@ -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")]
@ -68,7 +70,7 @@ pub enum TimestampError {
ByteConversion(ByteConversionError),
Cds(cds::CdsError),
Cuc(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>),
DateBeforeCcsdsEpoch(UnixTimestamp),
CustomEpochNotSupported,
}
@ -91,7 +93,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")
@ -231,59 +233,101 @@ pub trait CcsdsTimeProvider {
fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64;
fn subsecond_millis(&self) -> u16;
fn subsecond_nanos(&self) -> u32;
fn subsecond_millis(&self) -> u16 {
(self.subsecond_nanos() / 1_000_000) as u16
}
fn unix_stamp(&self) -> UnixTimestamp {
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis())
UnixTimestamp::new(self.unix_seconds(), self.subsecond_nanos())
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> Option<chrono::DateTime<chrono::Utc>>;
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_seconds(), self.subsecond_nanos())
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange>;
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(
time::OffsetDateTime::from_unix_timestamp(self.unix_seconds())?
+ time::Duration::nanoseconds(self.subsecond_nanos().into()),
)
}
}
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
///
/// 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.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimestamp {
pub unix_seconds: i64,
subsecond_millis: u16,
subsecond_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<Self> {
/// 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<Self> {
if subsec_nanos > 1_000_000_000 - 1 {
return None;
}
Some(Self::new(unix_seconds, subsec_nanos))
}
/// 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<Self> {
if subsec_millis > 999 {
return None;
}
Some(Self::const_new(unix_seconds, subsec_millis))
Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000)
}
/// 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");
/// 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 {
panic!("invalid subsecond nanos value");
}
Self {
unix_seconds,
subsecond_millis,
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 {
panic!("invalid subsecond millisecond value");
}
Self {
unix_seconds,
subsecond_nanos: subsecond_millis as u32 * 1_000_000,
}
}
pub fn new_only_seconds(unix_seconds: i64) -> Self {
Self {
unix_seconds,
subsecond_millis: 0,
subsecond_nanos: 0,
}
}
pub fn subsecond_millis(&self) -> u16 {
self.subsecond_millis
(self.subsecond_nanos / 1_000_000) as u16
}
pub fn subsecond_nanos(&self) -> u32 {
self.subsecond_nanos
}
#[cfg(feature = "std")]
@ -291,19 +335,16 @@ impl UnixTimestamp {
pub fn from_now() -> Result<Self, SystemTimeError> {
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)
self.unix_seconds as f64 + (self.subsecond_nanos as f64 / 1_000_000_000.0)
}
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
Utc.timestamp_opt(
self.unix_seconds,
self.subsecond_millis as u32 * 10_u32.pow(6),
)
Utc.timestamp_opt(self.unix_seconds, self.subsecond_nanos)
}
// Calculate the difference in milliseconds between two UnixTimestamps
@ -313,16 +354,16 @@ impl UnixTimestamp {
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.subsecond_nanos as i64 - other.subsecond_nanos as i64;
// Combine the differences
milliseconds_difference + subsecond_difference
milliseconds_difference + (subsecond_difference_nanos / 1_000_000)
}
}
impl From<DateTime<Utc>> for UnixTimestamp {
fn from(value: DateTime<Utc>) -> Self {
Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16)
Self::new(value.timestamp(), value.timestamp_subsec_nanos())
}
}
@ -395,7 +436,7 @@ 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_subsec_nanos = current_stamp.subsecond_nanos() + duration.subsec_nanos();
let mut new_unix_seconds = current_stamp.unix_seconds;
let mut increment_seconds = |value: u32| {
if new_unix_seconds < 0 {
@ -408,8 +449,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(
@ -418,7 +459,7 @@ fn get_new_stamp_after_addition(
.try_into()
.expect("duration seconds exceeds u32::MAX"),
);
UnixTimestamp::const_new(new_unix_seconds, new_subsec_millis)
UnixTimestamp::new(new_unix_seconds, new_subsec_nanos)
}
/// Please note that this operation will panic on the following conditions:
@ -457,10 +498,15 @@ impl Add<Duration> 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: UnixTimestamp = UnixTimestamp::new(5, 999_999_999);
#[allow(dead_code)]
const UNIX_STAMP_CONST_2: UnixTimestamp = UnixTimestamp::new_subsec_millis(5, 999);
#[test]
fn test_days_conversion() {
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
@ -506,17 +552,18 @@ mod tests {
#[test]
fn basic_float_unix_stamp_test() {
let stamp = UnixTimestamp::new(500, 600).unwrap();
let stamp = UnixTimestamp::new_subsec_millis_checked(500, 600).unwrap();
assert_eq!(stamp.unix_seconds, 500);
let subsec_millis = stamp.subsecond_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);
}
#[test]
fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0);
@ -526,7 +573,7 @@ mod tests {
#[test]
fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap();
let stamp1 = UnixTimestamp::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2);
@ -536,7 +583,7 @@ mod tests {
#[test]
fn test_ord_larger_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2);
@ -548,7 +595,7 @@ mod tests {
#[test]
fn test_ord_smaller_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap();
let stamp1 = UnixTimestamp::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0);
@ -560,7 +607,7 @@ mod tests {
#[allow(clippy::nonminimal_bool)]
#[test]
fn test_eq() {
let stamp0 = UnixTimestamp::new(5, 0).unwrap();
let stamp0 = UnixTimestamp::new(5, 0);
let stamp1 = UnixTimestamp::new_only_seconds(5);
assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1);
@ -582,7 +629,7 @@ mod tests {
#[test]
fn test_addition_on_ref() {
let stamp0 = &UnixTimestamp::new(20, 500).unwrap();
let stamp0 = &UnixTimestamp::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);
@ -609,19 +656,19 @@ mod tests {
#[test]
fn test_stamp_diff_positive_0() {
let stamp_later = UnixTimestamp::new(2, 0).unwrap();
let stamp_later = UnixTimestamp::new(2, 0);
let StampDiff {
positive_duration,
duration_absolute,
} = stamp_later - UnixTimestamp::new(1, 0).unwrap();
} = stamp_later - UnixTimestamp::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 = UnixTimestamp::new(3, 800 * 1_000_000);
let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff {
positive_duration,
duration_absolute,
@ -632,8 +679,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 = UnixTimestamp::new_subsec_millis_checked(3, 800).unwrap();
let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff {
positive_duration,
duration_absolute,
@ -644,7 +691,7 @@ mod tests {
#[test]
fn test_addition_spillover() {
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap();
let mut stamp0 = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2);
assert_eq!(stamp0.subsecond_millis(), 0);