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] # [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 ## Fixed
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI - 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` - Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
feature gate 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 # [v0.11.0-rc.0] 2024-03-04
## Added ## Added

View File

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

View File

@ -4,20 +4,40 @@
//! The core data structure to do this is the [TimeProvider] struct and the //! 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 //! [get_dyn_time_provider_from_bytes] function to retrieve correct instances of the
//! struct from a bytestream. //! struct from a bytestream.
use super::*;
use crate::private::Sealed; use crate::private::Sealed;
#[cfg(feature = "alloc")] use crate::ByteConversionError;
use alloc::boxed::Box;
#[cfg(feature = "chrono")]
use chrono::Datelike;
#[cfg(feature = "alloc")]
use core::any::Any;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::Debug; use core::fmt::{Debug, Display, Formatter};
use core::ops::{Add, AddAssign}; use core::ops::{Add, AddAssign};
use core::time::Duration; use core::time::Duration;
use delegate::delegate; 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. /// 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 = (CcsdsTimeCodes::Cds as u8) << 4;
pub const MIN_CDS_FIELD_LEN: usize = 7; pub const MIN_CDS_FIELD_LEN: usize = 7;
@ -165,7 +185,6 @@ pub struct TimeProvider<DaysLen: ProvidesDaysLength = DaysLen16Bits> {
pfield: u8, pfield: u8,
ccsds_days: DaysLen::FieldType, ccsds_days: DaysLen::FieldType,
ms_of_day: u32, ms_of_day: u32,
// submillis_precision: SubmillisPrecision,
submillis: u32, submillis: u32,
/// This is not strictly necessary but still cached because it significantly simplifies the /// This is not strictly necessary but still cached because it significantly simplifies the
/// calculation of [`DateTime<Utc>`]. /// calculation of [`DateTime<Utc>`].
@ -195,36 +214,47 @@ trait CdsConverter: CdsCommon {
struct ConversionFromUnix { struct ConversionFromUnix {
ccsds_days: u32, ccsds_days: u32,
ms_of_day: 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 /// 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. /// re-calculating the datetime at a later point and therefore supplied as well.
unix_days_seconds: i64, unix_days_seconds: i64,
} }
impl ConversionFromUnix { 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 (unix_days, secs_of_day) = calc_unix_days_and_secs_of_day(unix_seconds);
let ccsds_days = unix_to_ccsds_days(unix_days); let ccsds_days = unix_to_ccsds_days(unix_days);
if ccsds_days == 0 && (secs_of_day > 0 || subsec_millis > 0) || ccsds_days < 0 { if ccsds_days == 0 && (secs_of_day > 0 || subsec_nanos > 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
};
return Err(TimestampError::DateBeforeCcsdsEpoch( 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 { Ok(Self {
ccsds_days: unix_to_ccsds_days(unix_days) as u32, 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, unix_days_seconds: unix_days * SECONDS_PER_DAY as i64,
submilis_prec: precision,
submillis,
}) })
} }
} }
impl CdsCommon for ConversionFromUnix { impl CdsCommon for ConversionFromUnix {
fn submillis_precision(&self) -> SubmillisPrecision { fn submillis_precision(&self) -> SubmillisPrecision {
SubmillisPrecision::Absent self.submilis_prec
} }
fn ms_of_day(&self) -> u32 { fn ms_of_day(&self) -> u32 {
@ -236,7 +266,7 @@ impl CdsCommon for ConversionFromUnix {
} }
fn submillis(&self) -> u32 { 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. /// Helper struct which generates fields for the CDS time provider from a datetime.
struct ConversionFromDatetime { struct ConversionFromChronoDatetime {
unix_conversion: ConversionFromUnix, unix_conversion: ConversionFromUnix,
submillis_prec: SubmillisPrecision, submillis_prec: SubmillisPrecision,
submillis: u32, submillis: u32,
} }
impl CdsCommon for ConversionFromDatetime { impl CdsCommon for ConversionFromChronoDatetime {
fn submillis_precision(&self) -> SubmillisPrecision { fn submillis_precision(&self) -> SubmillisPrecision {
self.submillis_prec 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; }} 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) (unix_days, secs_of_day as u32)
} }
impl ConversionFromDatetime { #[cfg(feature = "chrono")]
fn new(dt: &DateTime<Utc>) -> Result<Self, TimestampError> { impl ConversionFromChronoDatetime {
fn new(dt: &chrono::DateTime<chrono::Utc>) -> Result<Self, TimestampError> {
Self::new_generic(dt, SubmillisPrecision::Absent) 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) 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) 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. // The CDS timestamp does not support timestamps before the CCSDS epoch.
if dt.year() < 1958 { 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 // The contained values in the conversion should be all positive now
let unix_conversion = let unix_conversion =
ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_millis())?; ConversionFromUnix::new(dt.timestamp(), dt.timestamp_subsec_nanos(), prec)?;
let mut submillis = 0; let mut submillis = 0;
match prec { match prec {
SubmillisPrecision::Microseconds => { SubmillisPrecision::Microseconds => {
@ -351,7 +392,8 @@ impl ConversionFromNow {
let epoch = now.as_secs(); let epoch = now.as_secs();
// This should always return a value with valid (non-negative) CCSDS days, // This should always return a value with valid (non-negative) CCSDS days,
// so it is okay to unwrap // 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; let mut submillis = 0;
match prec { match prec {
@ -589,20 +631,6 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
self.calc_unix_seconds(unix_days_seconds, ms_of_day); 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] #[inline]
fn calc_unix_seconds(&mut self, mut unix_days_seconds: i64, ms_of_day: u32) { 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; let seconds_of_day = (ms_of_day / 1000) as i64;
@ -611,35 +639,11 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
} else { } else {
unix_days_seconds += seconds_of_day; unix_days_seconds += seconds_of_day;
} }
self.unix_stamp = UnixTimestamp::const_new(unix_days_seconds, (ms_of_day % 1000) as u16); let mut subsec_nanos = (ms_of_day % 1000) * 10_u32.pow(6);
} if let Some(precision) = self.precision_as_ns() {
subsec_nanos += precision;
#[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);
} }
None self.unix_stamp = UnixTimestamp::new(unix_days_seconds, subsec_nanos);
}
#[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()),
)
} }
fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> { fn length_check(&self, buf: &[u8], len_as_bytes: usize) -> Result<(), TimestampError> {
@ -674,36 +678,43 @@ impl<ProvidesDaysLen: ProvidesDaysLength> TimeProvider<ProvidesDaysLen> {
Ok(provider) Ok(provider)
} }
#[cfg(feature = "chrono")]
fn from_dt_generic( fn from_dt_generic(
dt: &DateTime<Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> { ) -> 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) Self::generic_from_conversion(days_len, conv_from_dt)
} }
#[cfg(feature = "chrono")]
fn from_dt_generic_us_prec( fn from_dt_generic_us_prec(
dt: &DateTime<Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> { ) -> 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) Self::generic_from_conversion(days_len, conv_from_dt)
} }
#[cfg(feature = "chrono")]
fn from_dt_generic_ps_prec( fn from_dt_generic_ps_prec(
dt: &DateTime<Utc>, dt: &chrono::DateTime<chrono::Utc>,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
) -> Result<Self, TimestampError> { ) -> 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) Self::generic_from_conversion(days_len, conv_from_dt)
} }
fn from_unix_generic( fn from_unix_generic(
unix_stamp: &UnixTimestamp, unix_stamp: &UnixTimestamp,
days_len: LengthOfDaySegment, days_len: LengthOfDaySegment,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> { ) -> Result<Self, TimestampError> {
let conv_from_dt = let conv_from_dt = ConversionFromUnix::new(
ConversionFromUnix::new(unix_stamp.unix_seconds, unix_stamp.subsecond_millis as u32)?; unix_stamp.unix_seconds,
unix_stamp.subsecond_nanos,
submillis_prec,
)?;
Self::generic_from_conversion(days_len, conv_from_dt) Self::generic_from_conversion(days_len, conv_from_dt)
} }
@ -818,7 +829,10 @@ impl TimeProvider<DaysLen24Bits> {
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// [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). /// 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) Self::from_dt_generic(dt, LengthOfDaySegment::Long24Bits)
} }
@ -829,19 +843,26 @@ impl TimeProvider<DaysLen24Bits> {
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// [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). /// 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, unix_stamp: &UnixTimestamp,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> { ) -> 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. /// 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) Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Long24Bits)
} }
/// Like [Self::from_dt_with_u24_days] but with picoseconds sub-millisecond precision. /// 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) 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 /// 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 /// [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). /// 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) Self::from_dt_generic(dt, LengthOfDaySegment::Short16Bits)
} }
@ -916,19 +940,26 @@ impl TimeProvider<DaysLen16Bits> {
/// This function will return [TimestampError::DateBeforeCcsdsEpoch] or /// This function will return [TimestampError::DateBeforeCcsdsEpoch] or
/// [TimestampError::Cds] if the time is before the CCSDS epoch (1958-01-01T00:00:00+00:00) /// [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). /// 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, unix_stamp: &UnixTimestamp,
submillis_prec: SubmillisPrecision,
) -> Result<Self, TimestampError> { ) -> 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. /// 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) Self::from_dt_generic_us_prec(dt, LengthOfDaySegment::Short16Bits)
} }
/// Like [Self::from_dt_with_u16_days] but with picoseconds sub-millisecond precision. /// 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) 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; type Error = TimestampError;
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> { fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromDatetime::new(&dt)?; let conversion = ConversionFromChronoDatetime::new(&dt)?;
Self::generic_from_conversion(LengthOfDaySegment::Short16Bits, conversion) 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; type Error = TimestampError;
fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> { fn try_from(dt: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
let conversion = ConversionFromDatetime::new(&dt)?; let conversion = ConversionFromChronoDatetime::new(&dt)?;
Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion) Self::generic_from_conversion(LengthOfDaySegment::Long24Bits, conversion)
} }
} }
@ -1176,26 +1209,14 @@ impl<ProvidesDaysLen: ProvidesDaysLength> CcsdsTimeProvider for TimeProvider<Pro
self.unix_stamp.unix_seconds self.unix_stamp.unix_seconds
} }
#[inline] #[inline]
fn subsecond_millis(&self) -> u16 { fn subsecond_nanos(&self) -> u32 {
self.unix_stamp.subsecond_millis self.unix_stamp.subsecond_nanos
} }
#[inline] #[inline]
fn unix_stamp(&self) -> UnixTimestamp { fn unix_stamp(&self) -> UnixTimestamp {
self.unix_stamp 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> { impl TimeReader for TimeProvider<DaysLen16Bits> {
@ -1330,6 +1351,7 @@ impl TryFrom<TimeProvider<DaysLen24Bits>> for TimeProvider<DaysLen16Bits> {
mod tests { mod tests {
use super::*; use super::*;
use crate::time::TimestampError::{ByteConversion, InvalidTimeCode}; use crate::time::TimestampError::{ByteConversion, InvalidTimeCode};
use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, MS_PER_DAY};
use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall}; use crate::ByteConversionError::{FromSliceTooSmall, ToSliceTooSmall};
use alloc::string::ToString; use alloc::string::ToString;
use chrono::{Datelike, NaiveDate, Timelike}; use chrono::{Datelike, NaiveDate, Timelike};
@ -1345,13 +1367,13 @@ mod tests {
unix_stamp.unix_seconds, unix_stamp.unix_seconds,
(DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32) as i64 (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!(subsecond_millis, 0);
assert_eq!( assert_eq!(
time_stamper.submillis_precision(), time_stamper.submillis_precision(),
SubmillisPrecision::Absent 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.ccdsd_time_code(), CcsdsTimeCodes::Cds);
assert_eq!( assert_eq!(
time_stamper.p_field(), time_stamper.p_field(),
@ -1382,8 +1404,10 @@ mod tests {
assert_eq!(date_time.minute(), 0); assert_eq!(date_time.minute(), 0);
assert_eq!(date_time.second(), 0); assert_eq!(date_time.second(), 0);
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 40); 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.subsecond_millis(), 40);
let time_stamper = TimeProvider::new_with_u16_days((-DAYS_CCSDS_TO_UNIX) as u16, 1040); 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.subsecond_millis(), 40);
} }
@ -1565,7 +1589,7 @@ mod tests {
fn generic_now_test<T: ProvidesDaysLength>( fn generic_now_test<T: ProvidesDaysLength>(
timestamp_now: TimeProvider<T>, timestamp_now: TimeProvider<T>,
compare_stamp: DateTime<Utc>, compare_stamp: chrono::DateTime<chrono::Utc>,
) { ) {
let dt = timestamp_now.chrono_date_time().unwrap(); let dt = timestamp_now.chrono_date_time().unwrap();
if compare_stamp.year() > dt.year() { if compare_stamp.year() > dt.year() {
@ -1591,35 +1615,35 @@ mod tests {
#[test] #[test]
fn test_time_now() { fn test_time_now() {
let timestamp_now = TimeProvider::from_now_with_u16_days().unwrap(); 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); generic_now_test(timestamp_now, compare_stamp);
} }
#[test] #[test]
fn test_time_now_us_prec() { fn test_time_now_us_prec() {
let timestamp_now = TimeProvider::from_now_with_u16_days_us_precision().unwrap(); 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); generic_now_test(timestamp_now, compare_stamp);
} }
#[test] #[test]
fn test_time_now_ps_prec() { fn test_time_now_ps_prec() {
let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); 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); generic_now_test(timestamp_now, compare_stamp);
} }
#[test] #[test]
fn test_time_now_ps_prec_u16_days() { fn test_time_now_ps_prec_u16_days() {
let timestamp_now = TimeProvider::from_now_with_u16_days_ps_precision().unwrap(); 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); generic_now_test(timestamp_now, compare_stamp);
} }
#[test] #[test]
fn test_time_now_ps_prec_u24_days() { fn test_time_now_ps_prec_u24_days() {
let timestamp_now = TimeProvider::from_now_with_u24_days_ps_precision().unwrap(); 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); generic_now_test(timestamp_now, compare_stamp);
} }
@ -1743,19 +1767,19 @@ mod tests {
assert_eq!(stamp_deserialized.submillis(), 5e8 as u32); 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) NaiveDate::from_ymd_opt(2023, 1, 14)
.unwrap() .unwrap()
.and_hms_milli_opt(16, 49, 30, subsec_millis) .and_hms_milli_opt(16, 49, 30, subsec_millis)
.unwrap() .unwrap()
.and_local_timezone(Utc) .and_local_timezone(chrono::Utc)
.unwrap() .unwrap()
} }
fn generic_check_dt_case_0<DaysLen: ProvidesDaysLength>( fn generic_check_dt_case_0<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>, time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32, 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 // 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. // Leap years need to be accounted for as well.
@ -1790,21 +1814,21 @@ mod tests {
assert_eq!(time_provider, time_provider_2); 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 // 250 ms + 500 us
let subsec_micros = subsec_millis * 1000 + 500; let subsec_micros = subsec_millis * 1000 + 500;
NaiveDate::from_ymd_opt(2023, 1, 14) NaiveDate::from_ymd_opt(2023, 1, 14)
.unwrap() .unwrap()
.and_hms_micro_opt(16, 49, 30, subsec_micros) .and_hms_micro_opt(16, 49, 30, subsec_micros)
.unwrap() .unwrap()
.and_local_timezone(Utc) .and_local_timezone(chrono::Utc)
.unwrap() .unwrap()
} }
fn generic_check_dt_case_1_us_prec<DaysLen: ProvidesDaysLength>( fn generic_check_dt_case_1_us_prec<DaysLen: ProvidesDaysLength>(
time_provider: &TimeProvider<DaysLen>, time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32, 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 // 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. // 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); 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 // 250 ms + 500 us
let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000; let subsec_nanos = subsec_millis * 1000 * 1000 + 500 * 1000;
let submilli_nanos = subsec_nanos % 10_u32.pow(6); let submilli_nanos = subsec_nanos % 10_u32.pow(6);
@ -1848,7 +1872,7 @@ mod tests {
.unwrap() .unwrap()
.and_hms_nano_opt(16, 49, 30, subsec_nanos) .and_hms_nano_opt(16, 49, 30, subsec_nanos)
.unwrap() .unwrap()
.and_local_timezone(Utc) .and_local_timezone(chrono::Utc)
.unwrap(), .unwrap(),
submilli_nanos, submilli_nanos,
) )
@ -1858,7 +1882,7 @@ mod tests {
time_provider: &TimeProvider<DaysLen>, time_provider: &TimeProvider<DaysLen>,
subsec_millis: u32, subsec_millis: u32,
submilli_nanos: 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 // 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. // Leap years need to be accounted for as well.
@ -1907,10 +1931,10 @@ mod tests {
fn test_creation_from_unix_stamp_0_u16_days() { fn test_creation_from_unix_stamp_0_u16_days() {
let unix_secs = 0; let unix_secs = 0;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
unix_secs, &UnixTimestamp::new(unix_secs, subsec_millis),
subsec_millis, SubmillisPrecision::Absent,
)) )
.expect("creating provider from unix stamp failed"); .expect("creating provider from unix stamp failed");
assert_eq!(time_provider.ccsds_days, -DAYS_CCSDS_TO_UNIX as u16) 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() { fn test_creation_from_unix_stamp_0_u24_days() {
let unix_secs = 0; let unix_secs = 0;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = TimeProvider::from_unix_secs_with_u24_days(&UnixTimestamp::const_new( let time_provider = TimeProvider::from_unix_stamp_with_u24_days(
unix_secs, &UnixTimestamp::new(unix_secs, subsec_millis),
subsec_millis, SubmillisPrecision::Absent,
)) )
.expect("creating provider from unix stamp failed"); .expect("creating provider from unix stamp failed");
assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32) assert_eq!(time_provider.ccsds_days, (-DAYS_CCSDS_TO_UNIX) as u32)
} }
@ -1934,10 +1958,13 @@ mod tests {
.unwrap() .unwrap()
.and_hms_milli_opt(16, 49, 30, subsec_millis) .and_hms_milli_opt(16, 49, 30, subsec_millis)
.unwrap() .unwrap()
.and_local_timezone(Utc) .and_local_timezone(chrono::Utc)
.unwrap(); .unwrap();
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&datetime_utc.into()) let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
.expect("creating provider from unix stamp failed"); &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 // 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. // Leap years need to be accounted for as well.
assert_eq!(time_provider.ccsds_days, 23754); assert_eq!(time_provider.ccsds_days, 23754);
@ -1953,10 +1980,10 @@ mod tests {
fn test_creation_0_ccsds_days() { fn test_creation_0_ccsds_days() {
let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64; let unix_secs = DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64;
let subsec_millis = 0; let subsec_millis = 0;
let time_provider = TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( let time_provider = TimeProvider::from_unix_stamp_with_u16_days(
unix_secs, &UnixTimestamp::new(unix_secs, subsec_millis),
subsec_millis, SubmillisPrecision::Absent,
)) )
.expect("creating provider from unix stamp failed"); .expect("creating provider from unix stamp failed");
assert_eq!(time_provider.ccsds_days, 0) assert_eq!(time_provider.ccsds_days, 0)
} }
@ -1965,10 +1992,10 @@ mod tests {
fn test_invalid_creation_from_unix_stamp_days_too_large() { 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 invalid_unix_secs: i64 = (u16::MAX as i64 + 1) * SECONDS_PER_DAY as i64;
let subsec_millis = 0; let subsec_millis = 0;
match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( match TimeProvider::from_unix_stamp_with_u16_days(
invalid_unix_secs, &UnixTimestamp::new(invalid_unix_secs, subsec_millis),
subsec_millis, SubmillisPrecision::Absent,
)) { ) {
Ok(_) => { Ok(_) => {
panic!("creation should not succeed") panic!("creation should not succeed")
} }
@ -1992,21 +2019,26 @@ mod tests {
// precisely 31-12-1957 23:59:55 // precisely 31-12-1957 23:59:55
let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5; let unix_secs = DAYS_CCSDS_TO_UNIX * SECONDS_PER_DAY as i32 - 5;
let subsec_millis = 0; let subsec_millis = 0;
match TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::const_new( match TimeProvider::from_unix_stamp_with_u16_days(
unix_secs as i64, &UnixTimestamp::new(unix_secs as i64, subsec_millis),
subsec_millis, SubmillisPrecision::Absent,
)) { ) {
Ok(_) => { Ok(_) => {
panic!("creation should not succeed") panic!("creation should not succeed")
} }
Err(e) => { Err(e) => {
if let TimestampError::DateBeforeCcsdsEpoch(dt) = e { if let TimestampError::DateBeforeCcsdsEpoch(dt) = e {
assert_eq!(dt.year(), 1957); let dt = dt.as_date_time();
assert_eq!(dt.month(), 12); if let chrono::LocalResult::Single(dt) = dt {
assert_eq!(dt.day(), 31); assert_eq!(dt.year(), 1957);
assert_eq!(dt.hour(), 23); assert_eq!(dt.month(), 12);
assert_eq!(dt.minute(), 59); assert_eq!(dt.day(), 31);
assert_eq!(dt.second(), 55); assert_eq!(dt.hour(), 23);
assert_eq!(dt.minute(), 59);
assert_eq!(dt.second(), 55);
} else {
panic!("unexpected error {}", e)
}
} else { } else {
panic!("unexpected error {}", e) panic!("unexpected error {}", e)
} }
@ -2222,12 +2254,12 @@ mod tests {
.unwrap() .unwrap()
.and_hms_milli_opt(23, 59, 59, 999) .and_hms_milli_opt(23, 59, 59, 999)
.unwrap() .unwrap()
.and_local_timezone(Utc) .and_local_timezone(chrono::Utc)
.unwrap(); .unwrap();
let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc); let time_provider = TimeProvider::from_dt_with_u24_days(&datetime_utc);
assert!(time_provider.is_err()); assert!(time_provider.is_err());
if let TimestampError::DateBeforeCcsdsEpoch(dt) = time_provider.unwrap_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 . //! [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. //! 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")] #[cfg(feature = "chrono")]
use chrono::Datelike; use chrono::Datelike;
use core::fmt::Debug; #[cfg(feature = "alloc")]
use core::ops::{Add, AddAssign}; use super::ccsds_time_code_from_p_field;
use core::time::Duration;
use core::u64;
const MIN_CUC_LEN: usize = 2; const MIN_CUC_LEN: usize = 2;
@ -301,6 +315,7 @@ impl TimeProviderCcsdsEpoch {
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc_cfg, doc(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) -> Result<(), StdTimestampError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; 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 = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u32;
self.counter self.counter
@ -325,7 +340,9 @@ impl TimeProviderCcsdsEpoch {
) -> Result<Self, TimestampError> { ) -> Result<Self, TimestampError> {
// Year before CCSDS epoch is invalid. // Year before CCSDS epoch is invalid.
if dt.year() < 1958 { if dt.year() < 1958 {
return Err(TimestampError::DateBeforeCcsdsEpoch(*dt)); return Err(TimestampError::DateBeforeCcsdsEpoch(UnixTimestamp::from(
*dt,
)));
} }
let counter = dt let counter = dt
.timestamp() .timestamp()
@ -349,9 +366,7 @@ impl TimeProviderCcsdsEpoch {
let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds); let ccsds_epoch = unix_epoch_to_ccsds_epoch(unix_stamp.unix_seconds);
// Negative CCSDS epoch is invalid. // Negative CCSDS epoch is invalid.
if ccsds_epoch < 0 { if ccsds_epoch < 0 {
return Err(TimestampError::DateBeforeCcsdsEpoch( return Err(TimestampError::DateBeforeCcsdsEpoch(*unix_stamp));
unix_stamp.as_date_time().unwrap(),
));
} }
ccsds_epoch ccsds_epoch
.checked_add(i64::from(leap_seconds)) .checked_add(i64::from(leap_seconds))
@ -682,43 +697,16 @@ impl CcsdsTimeProvider for TimeProviderCcsdsEpoch {
self.unix_seconds() self.unix_seconds()
} }
fn subsecond_millis(&self) -> u16 { fn subsecond_nanos(&self) -> u32 {
if let Some(fractions) = self.fractions { if let Some(fractions) = self.fractions {
if fractions.0 == FractionalResolution::Seconds { if fractions.0 == FractionalResolution::Seconds {
return 0; return 0;
} }
// Rounding down here is the correct approach. // 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 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( fn get_provider_values_after_duration_addition(
@ -818,9 +806,11 @@ impl Add<Duration> for &TimeProviderCcsdsEpoch {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::time::{UnixTimestamp, DAYS_CCSDS_TO_UNIX, SECONDS_PER_DAY};
use super::*; use super::*;
use alloc::string::ToString; use alloc::string::ToString;
use chrono::{Datelike, Timelike}; use chrono::{Datelike, TimeZone, Timelike};
#[allow(unused_imports)] #[allow(unused_imports)]
use std::println; use std::println;
@ -840,14 +830,14 @@ mod tests {
let fractions = zero_cuc.width_fractions_pair(); let fractions = zero_cuc.width_fractions_pair();
assert!(fractions.is_none()); assert!(fractions.is_none());
let dt = zero_cuc.chrono_date_time(); let dt = zero_cuc.chrono_date_time();
assert!(dt.is_some()); if let chrono::LocalResult::Single(dt) = dt {
let dt = dt.unwrap(); assert_eq!(dt.year(), 1958);
assert_eq!(dt.year(), 1958); assert_eq!(dt.month(), 1);
assert_eq!(dt.month(), 1); assert_eq!(dt.day(), 1);
assert_eq!(dt.day(), 1); assert_eq!(dt.hour(), 0);
assert_eq!(dt.hour(), 0); assert_eq!(dt.minute(), 0);
assert_eq!(dt.minute(), 0); assert_eq!(dt.second(), 0);
assert_eq!(dt.second(), 0); }
} }
#[test] #[test]
@ -862,7 +852,7 @@ mod tests {
let zero_cuc = zero_cuc.unwrap(); let zero_cuc = zero_cuc.unwrap();
let res = zero_cuc.write_to_bytes(&mut buf); let res = zero_cuc.write_to_bytes(&mut buf);
assert!(res.is_ok()); 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!(zero_cuc.len_as_bytes(), 5);
assert_eq!(pfield_len(buf[0]), 1); assert_eq!(pfield_len(buf[0]), 1);
let written = res.unwrap(); let written = res.unwrap();
@ -880,17 +870,19 @@ mod tests {
#[test] #[test]
fn test_datetime_now() { fn test_datetime_now() {
let now = Utc::now(); let now = chrono::Utc::now();
let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS); let cuc_now = TimeProviderCcsdsEpoch::from_now(FractionalResolution::SixtyNs, LEAP_SECONDS);
assert!(cuc_now.is_ok()); assert!(cuc_now.is_ok());
let cuc_now = cuc_now.unwrap(); let cuc_now = cuc_now.unwrap();
let dt_opt = cuc_now.chrono_date_time(); let dt_opt = cuc_now.chrono_date_time();
assert!(dt_opt.is_some()); if let chrono::LocalResult::Single(dt) = dt_opt {
let dt = dt_opt.unwrap(); let diff = dt - now;
let diff = dt - now; assert!(diff.num_milliseconds() < 1000);
assert!(diff.num_milliseconds() < 1000); println!("datetime from cuc: {}", dt);
println!("datetime from cuc: {}", dt); println!("datetime now: {}", now);
println!("datetime now: {}", now); } else {
panic!("date time creation from now failed")
}
} }
#[test] #[test]
@ -1316,7 +1308,7 @@ mod tests {
#[test] #[test]
fn test_from_dt() { 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( let cuc = TimeProviderCcsdsEpoch::from_chrono_date_time(
&dt, &dt,
FractionalResolution::Seconds, FractionalResolution::Seconds,
@ -1335,7 +1327,7 @@ mod tests {
#[test] #[test]
fn from_unix_stamp() { 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( let cuc = TimeProviderCcsdsEpoch::from_unix_stamp(
&unix_stamp, &unix_stamp,
FractionalResolution::Seconds, 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) //! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
use crate::ByteConversionError; use crate::ByteConversionError;
#[cfg(feature = "chrono")]
use chrono::{DateTime, LocalResult, TimeZone, Utc}; use chrono::{DateTime, LocalResult, TimeZone, Utc};
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
@ -13,6 +14,7 @@ use num_traits::float::FloatCore;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error::Error; use std::error::Error;
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -68,7 +70,7 @@ pub enum TimestampError {
ByteConversion(ByteConversionError), ByteConversion(ByteConversionError),
Cds(cds::CdsError), Cds(cds::CdsError),
Cuc(cuc::CucError), Cuc(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>), DateBeforeCcsdsEpoch(UnixTimestamp),
CustomEpochNotSupported, CustomEpochNotSupported,
} }
@ -91,7 +93,7 @@ impl Display for TimestampError {
write!(f, "time stamp: {e}") write!(f, "time stamp: {e}")
} }
TimestampError::DateBeforeCcsdsEpoch(e) => { TimestampError::DateBeforeCcsdsEpoch(e) => {
write!(f, "datetime with date before ccsds epoch: {e}") write!(f, "datetime with date before ccsds epoch: {e:?}")
} }
TimestampError::CustomEpochNotSupported => { TimestampError::CustomEpochNotSupported => {
write!(f, "custom epochs are not supported") write!(f, "custom epochs are not supported")
@ -231,59 +233,101 @@ pub trait CcsdsTimeProvider {
fn ccdsd_time_code(&self) -> CcsdsTimeCodes; fn ccdsd_time_code(&self) -> CcsdsTimeCodes;
fn unix_seconds(&self) -> i64; 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 { 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(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(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(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(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. /// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00.
/// ///
/// Also can optionally include subsecond millisecond for greater accuracy. /// 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)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UnixTimestamp { pub struct UnixTimestamp {
pub unix_seconds: i64, pub unix_seconds: i64,
subsecond_millis: u16, subsecond_nanos: u32,
} }
impl UnixTimestamp { impl UnixTimestamp {
/// Returns [None] if the subsecond millisecond value is larger than 999. /// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a
pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option<Self> { /// 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 { if subsec_millis > 999 {
return None; 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. /// This function will panic if the subsecond value is larger than the fraction of a second.
pub const fn const_new(unix_seconds: i64, subsecond_millis: u16) -> Self { /// Use [new_checked] if you want to handle this case without a panic.
if subsecond_millis > 999 { pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self {
panic!("subsec milliseconds exceeds 999"); // 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 { Self {
unix_seconds, 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 { pub fn new_only_seconds(unix_seconds: i64) -> Self {
Self { Self {
unix_seconds, unix_seconds,
subsecond_millis: 0, subsecond_nanos: 0,
} }
} }
pub fn subsecond_millis(&self) -> u16 { 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")] #[cfg(feature = "std")]
@ -291,19 +335,16 @@ impl UnixTimestamp {
pub fn from_now() -> Result<Self, SystemTimeError> { pub fn from_now() -> Result<Self, SystemTimeError> {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let epoch = now.as_secs(); 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] #[inline]
pub fn unix_seconds_f64(&self) -> f64 { 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>> { pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> {
Utc.timestamp_opt( Utc.timestamp_opt(self.unix_seconds, self.subsecond_nanos)
self.unix_seconds,
self.subsecond_millis as u32 * 10_u32.pow(6),
)
} }
// Calculate the difference in milliseconds between two UnixTimestamps // Calculate the difference in milliseconds between two UnixTimestamps
@ -313,16 +354,16 @@ impl UnixTimestamp {
let milliseconds_difference = seconds_difference * 1000; let milliseconds_difference = seconds_difference * 1000;
// Calculate the difference in subsecond milliseconds directly // 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 // Combine the differences
milliseconds_difference + subsecond_difference milliseconds_difference + (subsecond_difference_nanos / 1_000_000)
} }
} }
impl From<DateTime<Utc>> for UnixTimestamp { impl From<DateTime<Utc>> for UnixTimestamp {
fn from(value: DateTime<Utc>) -> Self { 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, current_stamp: &UnixTimestamp,
duration: Duration, duration: Duration,
) -> UnixTimestamp { ) -> 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 new_unix_seconds = current_stamp.unix_seconds;
let mut increment_seconds = |value: u32| { let mut increment_seconds = |value: u32| {
if new_unix_seconds < 0 { if new_unix_seconds < 0 {
@ -408,8 +449,8 @@ fn get_new_stamp_after_addition(
.expect("new unix seconds would exceed i64::MAX"); .expect("new unix seconds would exceed i64::MAX");
} }
}; };
if new_subsec_millis >= 1000 { if new_subsec_nanos >= 1_000_000_000 {
new_subsec_millis -= 1000; new_subsec_nanos -= 1_000_000_000;
increment_seconds(1); increment_seconds(1);
} }
increment_seconds( increment_seconds(
@ -418,7 +459,7 @@ fn get_new_stamp_after_addition(
.try_into() .try_into()
.expect("duration seconds exceeds u32::MAX"), .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: /// Please note that this operation will panic on the following conditions:
@ -457,10 +498,15 @@ impl Add<Duration> for &UnixTimestamp {
mod tests { mod tests {
use alloc::string::ToString; use alloc::string::ToString;
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use std::format; use std::{format, println};
use super::{cuc::CucError, *}; 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] #[test]
fn test_days_conversion() { fn test_days_conversion() {
assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0); assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
@ -506,17 +552,18 @@ mod tests {
#[test] #[test]
fn basic_float_unix_stamp_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); assert_eq!(stamp.unix_seconds, 500);
let subsec_millis = stamp.subsecond_millis(); let subsec_millis = stamp.subsecond_millis();
assert_eq!(subsec_millis, 600); assert_eq!(subsec_millis, 600);
println!("{:?}", (500.6 - stamp.unix_seconds_f64()).to_string());
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001); assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001);
} }
#[test] #[test]
fn test_ord_larger() { fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5); 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); let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp1 > stamp0); assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0); assert!(stamp2 > stamp0);
@ -526,7 +573,7 @@ mod tests {
#[test] #[test]
fn test_ord_smaller() { fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5); 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); let stamp2 = UnixTimestamp::new_only_seconds(6);
assert!(stamp0 < stamp1); assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2); assert!(stamp0 < stamp2);
@ -536,7 +583,7 @@ mod tests {
#[test] #[test]
fn test_ord_larger_neg_numbers() { fn test_ord_larger_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5); 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); let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp0 > stamp1); assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2); assert!(stamp0 > stamp2);
@ -548,7 +595,7 @@ mod tests {
#[test] #[test]
fn test_ord_smaller_neg_numbers() { fn test_ord_smaller_neg_numbers() {
let stamp0 = UnixTimestamp::new_only_seconds(-5); 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); let stamp2 = UnixTimestamp::new_only_seconds(-6);
assert!(stamp2 < stamp1); assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0); assert!(stamp2 < stamp0);
@ -560,7 +607,7 @@ mod tests {
#[allow(clippy::nonminimal_bool)] #[allow(clippy::nonminimal_bool)]
#[test] #[test]
fn test_eq() { fn test_eq() {
let stamp0 = UnixTimestamp::new(5, 0).unwrap(); let stamp0 = UnixTimestamp::new(5, 0);
let stamp1 = UnixTimestamp::new_only_seconds(5); let stamp1 = UnixTimestamp::new_only_seconds(5);
assert_eq!(stamp0, stamp1); assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1); assert!(stamp0 <= stamp1);
@ -582,7 +629,7 @@ mod tests {
#[test] #[test]
fn test_addition_on_ref() { 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); let stamp1 = stamp0 + Duration::from_millis(2500);
assert_eq!(stamp1.unix_seconds, 23); assert_eq!(stamp1.unix_seconds, 23);
assert_eq!(stamp1.subsecond_millis(), 0); assert_eq!(stamp1.subsecond_millis(), 0);
@ -609,19 +656,19 @@ mod tests {
#[test] #[test]
fn test_stamp_diff_positive_0() { fn test_stamp_diff_positive_0() {
let stamp_later = UnixTimestamp::new(2, 0).unwrap(); let stamp_later = UnixTimestamp::new(2, 0);
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = stamp_later - UnixTimestamp::new(1, 0).unwrap(); } = stamp_later - UnixTimestamp::new(1, 0);
assert!(positive_duration); assert!(positive_duration);
assert_eq!(duration_absolute, Duration::from_secs(1)); assert_eq!(duration_absolute, Duration::from_secs(1));
} }
#[test] #[test]
fn test_stamp_diff_positive_1() { fn test_stamp_diff_positive_1() {
let stamp_later = UnixTimestamp::new(3, 800).unwrap(); let stamp_later = UnixTimestamp::new(3, 800 * 1_000_000);
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
@ -632,8 +679,8 @@ mod tests {
#[test] #[test]
fn test_stamp_diff_negative() { fn test_stamp_diff_negative() {
let stamp_later = UnixTimestamp::new(3, 800).unwrap(); let stamp_later = UnixTimestamp::new_subsec_millis_checked(3, 800).unwrap();
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); let stamp_earlier = UnixTimestamp::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
@ -644,7 +691,7 @@ mod tests {
#[test] #[test]
fn test_addition_spillover() { 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); stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2); assert_eq!(stamp0.unix_seconds, 2);
assert_eq!(stamp0.subsecond_millis(), 0); assert_eq!(stamp0.subsecond_millis(), 0);