Major refactoring of the time API
All checks were successful
Rust/spacepackets/pipeline/head This commit looks good
Rust/spacepackets/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2024-03-18 15:14:40 +01:00
parent 6f5254bdbd
commit 59c7ece126
Signed by: muellerr
GPG Key ID: A649FB78196E3849
8 changed files with 1202 additions and 810 deletions

View File

@ -8,9 +8,41 @@ 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 in the **Changed** chapter.
## Fixed
- CUC timestamp was fixed to include leap second corrections because it is based on the TAI
time reference. The default CUC time object do not implement `CcsdsTimeProvider` anymore
because the trait methods require cached leap second information. This task is now performed
by the `cuc::CucTimeWithLeapSecs` which implements the trait.
## Added ## Added
- `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions. - `From<$EcssEnum$TY> from $TY` for the ECSS enum type definitions.
- Added basic support conversions to the `time` library. Introduce new `chrono` and `timelib`
feature gate.
- Added `CcsdsTimeProvider::timelib_date_time`.
## Changed
- Renamed `CcsdsTimeProvider::date_time` to `CcsdsTimeProvider::chrono_date_time`
- Renamed `CcsdsTimeCodes` to `CcsdsTimeCode`
- Renamed `cds::TimeProvider` to `cds::CdsTime`
- Renamed `cuc::TimeProviderCcsdsEpoch` to `cuc::CucTime`
- `UnixTimestamp` renamed to `UnixTime`
- `UnixTime` seconds are now private and can be retrieved using the `secs` member method.
- `UnixTime::new` renamed to `UnixTime::new_checked`.
- `UnixTime` now has a nanosecond subsecond precision. The `new` constructor now expects
nanoseconds as the second argument.
- Added new `UnixTime::new_subsec_millis` and `UnixTime::new_subsec_millis_checked` API
to still allow creating a timestamp with only millisecond subsecond resolution.
- `CcsdsTimeProvider` now has a new `subsec_nanos` method in addition to a default
implementation for the `subsec_millis` method.
- `CcsdsTimeProvider::date_time` renamed to `CcsdsTimeProvider::chrono_date_time`.
- Added `UnixTime::MIN`, `UnixTime::MAX` and `UnixTime::EPOCH`.
- Added `UnixTime::timelib_date_time`.
# [v0.11.0-rc.0] 2024-03-04 # [v0.11.0-rc.0] 2024-03-04

View File

@ -34,22 +34,31 @@ optional = true
default-features = false default-features = false
features = ["derive"] features = ["derive"]
[dependencies.time]
version = "0.3"
default-features = false
optional = true
[dependencies.chrono] [dependencies.chrono]
version = "0.4" version = "0.4"
default-features = false default-features = false
optional = true
[dependencies.num-traits] [dependencies.num-traits]
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"]
std = ["chrono/std", "chrono/clock", "alloc", "thiserror"] std = ["chrono/std", "chrono/clock", "alloc", "thiserror"]
serde = ["dep:serde", "chrono/serde"] serde = ["dep:serde", "chrono/serde"]
alloc = ["postcard/alloc", "chrono/alloc"] alloc = ["postcard/alloc", "chrono/alloc"]
chrono = ["dep:chrono"]
timelib = ["dep:time"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -43,6 +43,8 @@ deserializing them with an appropriate `serde` provider like
## Optional Features ## Optional Features
- [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
- [`chrono`](https://crates.io/crates/chrono): Add basic support for the `chrono` time library.
- [`timelib`](https://crates.io/crates/time): Add basic support for the `time` time library.
# Examples # Examples

View File

@ -1097,7 +1097,7 @@ mod tests {
use super::*; use super::*;
use crate::ecss::PusVersion::PusC; use crate::ecss::PusVersion::PusC;
use crate::time::cds::TimeProvider; use crate::time::cds::CdsTime;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::time::CcsdsTimeProvider; use crate::time::CcsdsTimeProvider;
use crate::SpHeader; use crate::SpHeader;
@ -1136,7 +1136,7 @@ mod tests {
#[test] #[test]
fn test_basic_simple_api() { fn test_basic_simple_api() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0); let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8]; let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm = let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)
@ -1534,7 +1534,7 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serialization_creator_serde() { fn test_serialization_creator_serde() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0); let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8]; let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm = let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)
@ -1549,7 +1549,7 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn test_serialization_reader_serde() { fn test_serialization_reader_serde() {
let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap(); let mut sph = SpHeader::tm_unseg(0x123, 0x234, 0).unwrap();
let time_provider = TimeProvider::new_with_u16_days(0, 0); let time_provider = CdsTime::new_with_u16_days(0, 0);
let mut stamp_buf: [u8; 8] = [0; 8]; let mut stamp_buf: [u8; 8] = [0; 8];
let pus_tm = let pus_tm =
PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true) PusTmCreator::new_simple(&mut sph, 17, 2, &time_provider, &mut stamp_buf, None, true)

View File

@ -2,11 +2,8 @@
//! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 . //! [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) section 3.5 .
//! See [chrono::DateTime::format] for a usage example of the generated //! See [chrono::DateTime::format] for a usage example of the generated
//! [chrono::format::DelayedFormat] structs. //! [chrono::format::DelayedFormat] structs.
#[cfg(feature = "alloc")] #[cfg(all(feature = "alloc", feature = "chrono"))]
use chrono::{ pub use alloc_mod_chrono::*;
format::{DelayedFormat, StrftimeItems},
DateTime, Utc,
};
/// Tuple of format string and formatted size for time code A. /// Tuple of format string and formatted size for time code A.
/// ///
@ -34,36 +31,41 @@ pub const FMT_STR_CODE_B_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3f", 21);
/// Three digits are used for the decimal fraction and a terminator is added at the end. /// Three digits are used for the decimal fraction and a terminator is added at the end.
pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22); pub const FMT_STR_CODE_B_TERMINATED_WITH_SIZE: (&str, usize) = ("%Y-%jT%T%.3fZ", 22);
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format. #[cfg(all(feature = "alloc", feature = "chrono"))]
#[cfg(feature = "alloc")] pub mod alloc_mod_chrono {
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] use super::*;
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> { use chrono::{
format::{DelayedFormat, StrftimeItems},
DateTime, Utc,
};
/// Generates a time code formatter using the [FMT_STR_CODE_A_WITH_SIZE] format.
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
pub fn generate_time_code_a(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_WITH_SIZE.0) date.format(FMT_STR_CODE_A_WITH_SIZE.0)
} }
/// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_A_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub fn generate_time_code_a_terminated(
pub fn generate_time_code_a_terminated(
date: &DateTime<Utc>, date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> { ) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0) date.format(FMT_STR_CODE_A_TERMINATED_WITH_SIZE.0)
} }
/// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_B_WITH_SIZE] format.
#[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
pub fn generate_time_code_b(date: &DateTime<Utc>) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_WITH_SIZE.0) date.format(FMT_STR_CODE_B_WITH_SIZE.0)
} }
/// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format. /// Generates a time code formatter using the [FMT_STR_CODE_B_TERMINATED_WITH_SIZE] format.
#[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "alloc", feature = "chrono"))))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub fn generate_time_code_b_terminated(
pub fn generate_time_code_b_terminated(
date: &DateTime<Utc>, date: &DateTime<Utc>,
) -> DelayedFormat<StrftimeItems<'static>> { ) -> DelayedFormat<StrftimeItems<'static>> {
date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0) date.format(FMT_STR_CODE_B_TERMINATED_WITH_SIZE.0)
}
} }
#[cfg(test)] #[cfg(test)]
@ -77,7 +79,7 @@ mod tests {
let date = Utc::now(); let date = Utc::now();
let stamp_formatter = generate_time_code_a(&date); let stamp_formatter = generate_time_code_a(&date);
let stamp = format!("{}", stamp_formatter); let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T"); let t_sep = stamp.find('T');
assert!(t_sep.is_some()); assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10); assert_eq!(t_sep.unwrap(), 10);
assert_eq!(stamp.len(), FMT_STR_CODE_A_WITH_SIZE.1); assert_eq!(stamp.len(), FMT_STR_CODE_A_WITH_SIZE.1);
@ -88,10 +90,10 @@ mod tests {
let date = Utc::now(); let date = Utc::now();
let stamp_formatter = generate_time_code_a_terminated(&date); let stamp_formatter = generate_time_code_a_terminated(&date);
let stamp = format!("{}", stamp_formatter); let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T"); let t_sep = stamp.find('T');
assert!(t_sep.is_some()); assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 10); assert_eq!(t_sep.unwrap(), 10);
let z_terminator = stamp.find("Z"); let z_terminator = stamp.find('Z');
assert!(z_terminator.is_some()); assert!(z_terminator.is_some());
assert_eq!( assert_eq!(
z_terminator.unwrap(), z_terminator.unwrap(),
@ -105,7 +107,7 @@ mod tests {
let date = Utc::now(); let date = Utc::now();
let stamp_formatter = generate_time_code_b(&date); let stamp_formatter = generate_time_code_b(&date);
let stamp = format!("{}", stamp_formatter); let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T"); let t_sep = stamp.find('T');
assert!(t_sep.is_some()); assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8); assert_eq!(t_sep.unwrap(), 8);
assert_eq!(stamp.len(), FMT_STR_CODE_B_WITH_SIZE.1); assert_eq!(stamp.len(), FMT_STR_CODE_B_WITH_SIZE.1);
@ -116,10 +118,10 @@ mod tests {
let date = Utc::now(); let date = Utc::now();
let stamp_formatter = generate_time_code_b_terminated(&date); let stamp_formatter = generate_time_code_b_terminated(&date);
let stamp = format!("{}", stamp_formatter); let stamp = format!("{}", stamp_formatter);
let t_sep = stamp.find("T"); let t_sep = stamp.find('T');
assert!(t_sep.is_some()); assert!(t_sep.is_some());
assert_eq!(t_sep.unwrap(), 8); assert_eq!(t_sep.unwrap(), 8);
let z_terminator = stamp.find("Z"); let z_terminator = stamp.find('Z');
assert!(z_terminator.is_some()); assert!(z_terminator.is_some());
assert_eq!( assert_eq!(
z_terminator.unwrap(), z_terminator.unwrap(),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
//! 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;
use chrono::{DateTime, LocalResult, TimeZone, Utc}; #[cfg(feature = "chrono")]
use chrono::{TimeZone, Utc};
use core::cmp::Ordering; use core::cmp::Ordering;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
use core::ops::{Add, AddAssign, Sub}; use core::ops::{Add, AddAssign, Sub};
@ -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")]
@ -27,10 +29,11 @@ pub mod cuc;
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383; pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
pub const SECONDS_PER_DAY: u32 = 86400; pub const SECONDS_PER_DAY: u32 = 86400;
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000; pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CcsdsTimeCodes { pub enum CcsdsTimeCode {
CucCcsdsEpoch = 0b001, CucCcsdsEpoch = 0b001,
CucAgencyEpoch = 0b010, CucAgencyEpoch = 0b010,
Cds = 0b100, Cds = 0b100,
@ -38,16 +41,16 @@ pub enum CcsdsTimeCodes {
AgencyDefined = 0b110, AgencyDefined = 0b110,
} }
impl TryFrom<u8> for CcsdsTimeCodes { impl TryFrom<u8> for CcsdsTimeCode {
type Error = (); type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
x if x == CcsdsTimeCodes::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCodes::CucCcsdsEpoch), x if x == CcsdsTimeCode::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCode::CucCcsdsEpoch),
x if x == CcsdsTimeCodes::CucAgencyEpoch as u8 => Ok(CcsdsTimeCodes::CucAgencyEpoch), x if x == CcsdsTimeCode::CucAgencyEpoch as u8 => Ok(CcsdsTimeCode::CucAgencyEpoch),
x if x == CcsdsTimeCodes::Cds as u8 => Ok(CcsdsTimeCodes::Cds), x if x == CcsdsTimeCode::Cds as u8 => Ok(CcsdsTimeCode::Cds),
x if x == CcsdsTimeCodes::Ccs as u8 => Ok(CcsdsTimeCodes::Ccs), x if x == CcsdsTimeCode::Ccs as u8 => Ok(CcsdsTimeCode::Ccs),
x if x == CcsdsTimeCodes::AgencyDefined as u8 => Ok(CcsdsTimeCodes::AgencyDefined), x if x == CcsdsTimeCode::AgencyDefined as u8 => Ok(CcsdsTimeCode::AgencyDefined),
_ => Err(()), _ => Err(()),
} }
} }
@ -55,20 +58,20 @@ impl TryFrom<u8> for CcsdsTimeCodes {
/// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the /// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the
/// value of the raw time code identification field is returned. /// value of the raw time code identification field is returned.
pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCodes, u8> { pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
let raw_bits = (pfield >> 4) & 0b111; let raw_bits = (pfield >> 4) & 0b111;
CcsdsTimeCodes::try_from(raw_bits).map_err(|_| raw_bits) CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
} }
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive] #[non_exhaustive]
pub enum TimestampError { pub enum TimestampError {
InvalidTimeCode { expected: CcsdsTimeCodes, found: u8 }, InvalidTimeCode { expected: CcsdsTimeCode, found: u8 },
ByteConversion(ByteConversionError), ByteConversion(ByteConversionError),
Cds(cds::CdsError), Cds(cds::CdsError),
Cuc(cuc::CucError), Cuc(cuc::CucError),
DateBeforeCcsdsEpoch(DateTime<Utc>), DateBeforeCcsdsEpoch(UnixTime),
CustomEpochNotSupported, CustomEpochNotSupported,
} }
@ -91,7 +94,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")
@ -204,16 +207,15 @@ pub trait TimeWriter {
} }
} }
pub trait TimeReader { pub trait TimeReader: Sized {
fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError> fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>;
where
Self: Sized;
} }
/// Trait for generic CCSDS time providers. /// Trait for generic CCSDS time providers.
/// ///
/// The UNIX helper methods and the [Self::date_time] method are not strictly necessary but extremely /// The UNIX helper methods and the helper method are not strictly necessary but extremely
/// practical because they are a very common and simple exchange format for time information. /// practical because they are a very common and simple exchange format for time information.
/// Therefore, it was decided to keep them in this trait as well.
pub trait CcsdsTimeProvider { pub trait CcsdsTimeProvider {
fn len_as_bytes(&self) -> usize; fn len_as_bytes(&self) -> usize;
@ -222,56 +224,120 @@ pub trait CcsdsTimeProvider {
/// entry denotes the length of the pfield and the second entry is the value of the pfield /// entry denotes the length of the pfield and the second entry is the value of the pfield
/// in big endian format. /// in big endian format.
fn p_field(&self) -> (usize, [u8; 2]); fn p_field(&self) -> (usize, [u8; 2]);
fn ccdsd_time_code(&self) -> CcsdsTimeCodes; fn ccdsd_time_code(&self) -> CcsdsTimeCode;
fn unix_seconds(&self) -> i64; fn unix_secs(&self) -> i64;
fn subsecond_millis(&self) -> u16; fn subsec_nanos(&self) -> u32;
fn unix_stamp(&self) -> UnixTimestamp {
UnixTimestamp::const_new(self.unix_seconds(), self.subsecond_millis()) fn subsec_millis(&self) -> u16 {
(self.subsec_nanos() / 1_000_000) as u16
} }
fn date_time(&self) -> Option<DateTime<Utc>>; fn unix_time(&self) -> UnixTime {
UnixTime::new(self.unix_secs(), self.subsec_nanos())
}
#[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into()))
}
} }
/// UNIX timestamp: Elapsed seconds since 1970-01-01T00:00:00+00:00. /// UNIX time: Elapsed non-leap seconds since 1970-01-01T00:00:00+00:00 UTC.
/// ///
/// Also can optionally include subsecond millisecond for greater accuracy. /// This is a commonly used time format and can therefore also be used as a generic format to
/// convert other CCSDS time formats to and from. The subsecond precision is in nanoseconds
/// similarly to other common time formats and libraries.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[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 UnixTime {
pub unix_seconds: i64, secs: i64,
subsecond_millis: u16, subsec_nanos: u32,
} }
impl UnixTimestamp { impl UnixTime {
/// Returns [None] if the subsecond millisecond value is larger than 999. /// The UNIX epoch time: 1970-01-01T00:00:00+00:00 UTC.
pub fn new(unix_seconds: i64, subsec_millis: u16) -> Option<Self> { pub const EPOCH: Self = Self {
if subsec_millis > 999 { secs: 0,
subsec_nanos: 0,
};
/// The minimum possible `UnixTime`.
pub const MIN: Self = Self {
secs: i64::MIN,
subsec_nanos: 0,
};
/// The maximum possible `UnixTime`.
pub const MAX: Self = Self {
secs: i64::MAX,
subsec_nanos: NANOS_PER_SECOND - 1,
};
/// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a
/// second)
pub fn new_checked(unix_seconds: i64, subsec_nanos: u32) -> Option<Self> {
if subsec_nanos >= NANOS_PER_SECOND {
return None; return None;
} }
Some(Self::const_new(unix_seconds, subsec_millis)) Some(Self::new(unix_seconds, subsec_nanos))
} }
/// Like [Self::new] but const. Panics if the subsecond value is larger than 999. /// Returns [None] if the subsecond millisecond value is invalid (larger than fraction of a
pub const fn const_new(unix_seconds: i64, subsecond_millis: u16) -> Self { /// second)
if subsecond_millis > 999 { pub fn new_subsec_millis_checked(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
panic!("subsec milliseconds exceeds 999"); if subsec_millis >= 1000 {
return None;
}
Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000)
}
/// This function will panic if the subsecond value is larger than the fraction of a second.
/// Use [Self::new_checked] if you want to handle this case without a panic.
pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self {
if subsecond_nanos >= NANOS_PER_SECOND {
panic!("invalid subsecond nanos value");
} }
Self { Self {
unix_seconds, secs: unix_seconds,
subsecond_millis, subsec_nanos: subsecond_nanos,
} }
} }
pub fn new_only_seconds(unix_seconds: i64) -> Self { /// This function will panic if the subsecond value is larger than the fraction of a second.
/// Use [Self::new_subsec_millis_checked] if you want to handle this case without a panic.
pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self {
if subsecond_millis >= 1000 {
panic!("invalid subsecond millisecond value");
}
Self { Self {
unix_seconds, secs: unix_seconds,
subsecond_millis: 0, subsec_nanos: subsecond_millis as u32 * 1_000_000,
} }
} }
pub fn subsecond_millis(&self) -> u16 { pub fn new_only_secs(unix_seconds: i64) -> Self {
self.subsecond_millis Self {
secs: unix_seconds,
subsec_nanos: 0,
}
}
#[inline]
pub fn subsec_millis(&self) -> u16 {
(self.subsec_nanos / 1_000_000) as u16
}
pub fn subsec_nanos(&self) -> u32 {
self.subsec_nanos
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -279,68 +345,88 @@ 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_secs_f64(&self) -> f64 {
self.unix_seconds as f64 + (self.subsecond_millis as f64 / 1000.0) self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0)
} }
pub fn as_date_time(&self) -> LocalResult<DateTime<Utc>> { pub fn secs(&self) -> i64 {
Utc.timestamp_opt( self.secs
self.unix_seconds, }
self.subsecond_millis as u32 * 10_u32.pow(6),
) #[cfg(feature = "chrono")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
Utc.timestamp_opt(self.secs, self.subsec_nanos)
}
#[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
Ok(time::OffsetDateTime::from_unix_timestamp(self.secs())?
+ time::Duration::nanoseconds(self.subsec_nanos().into()))
} }
// Calculate the difference in milliseconds between two UnixTimestamps // Calculate the difference in milliseconds between two UnixTimestamps
pub fn difference_in_millis(&self, other: &UnixTimestamp) -> i64 { pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> {
let seconds_difference = self.unix_seconds - other.unix_seconds; let seconds_difference = self.secs.checked_sub(other.secs)?;
// Convert seconds difference to milliseconds // Convert seconds difference to milliseconds
let milliseconds_difference = seconds_difference * 1000; let milliseconds_difference = seconds_difference.checked_mul(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.subsec_nanos as i64 - other.subsec_nanos as i64;
// Combine the differences // Combine the differences
milliseconds_difference + subsecond_difference Some(milliseconds_difference + (subsecond_difference_nanos / 1_000_000))
} }
} }
impl From<DateTime<Utc>> for UnixTimestamp { #[cfg(feature = "chrono")]
fn from(value: DateTime<Utc>) -> Self { #[cfg_attr(doc_cfg, doc(cfg(feature = "chrono")))]
Self::const_new(value.timestamp(), value.timestamp_subsec_millis() as u16) impl From<chrono::DateTime<chrono::Utc>> for UnixTime {
fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
Self::new(value.timestamp(), value.timestamp_subsec_nanos())
} }
} }
impl PartialOrd for UnixTimestamp { #[cfg(feature = "timelib")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "timelib")))]
impl From<time::OffsetDateTime> for UnixTime {
fn from(value: time::OffsetDateTime) -> Self {
Self::new(value.unix_timestamp(), value.nanosecond())
}
}
impl PartialOrd for UnixTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for UnixTimestamp { impl Ord for UnixTime {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
if self == other { if self == other {
return Ordering::Equal; return Ordering::Equal;
} }
match self.unix_seconds.cmp(&other.unix_seconds) { match self.secs.cmp(&other.secs) {
Ordering::Less => return Ordering::Less, Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater, Ordering::Greater => return Ordering::Greater,
_ => (), _ => (),
} }
match self.subsecond_millis().cmp(&other.subsecond_millis()) { match self.subsec_millis().cmp(&other.subsec_millis()) {
Ordering::Less => { Ordering::Less => {
return if self.unix_seconds < 0 { return if self.secs < 0 {
Ordering::Greater Ordering::Greater
} else { } else {
Ordering::Less Ordering::Less
} }
} }
Ordering::Greater => { Ordering::Greater => {
return if self.unix_seconds < 0 { return if self.secs < 0 {
Ordering::Less Ordering::Less
} else { } else {
Ordering::Greater Ordering::Greater
@ -360,12 +446,12 @@ pub struct StampDiff {
pub duration_absolute: Duration, pub duration_absolute: Duration,
} }
impl Sub for UnixTimestamp { impl Sub for UnixTime {
type Output = StampDiff; type Output = Option<StampDiff>;
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
let difference = self.difference_in_millis(&rhs); let difference = self.diff_in_millis(&rhs)?;
if difference < 0 { Some(if difference < 0 {
StampDiff { StampDiff {
positive_duration: false, positive_duration: false,
duration_absolute: Duration::from_millis(-difference as u64), duration_absolute: Duration::from_millis(-difference as u64),
@ -375,16 +461,13 @@ impl Sub for UnixTimestamp {
positive_duration: true, positive_duration: true,
duration_absolute: Duration::from_millis(difference as u64), duration_absolute: Duration::from_millis(difference as u64),
} }
} })
} }
} }
fn get_new_stamp_after_addition( fn get_new_stamp_after_addition(current_stamp: &UnixTime, duration: Duration) -> UnixTime {
current_stamp: &UnixTimestamp, let mut new_subsec_nanos = current_stamp.subsec_nanos() + duration.subsec_nanos();
duration: Duration, let mut new_unix_seconds = current_stamp.secs;
) -> UnixTimestamp {
let mut new_subsec_millis = current_stamp.subsecond_millis() + duration.subsec_millis() as u16;
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 {
new_unix_seconds = new_unix_seconds new_unix_seconds = new_unix_seconds
@ -396,8 +479,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(
@ -406,7 +489,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) UnixTime::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:
@ -414,7 +497,7 @@ fn get_new_stamp_after_addition(
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN]. /// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX]. /// - Unix seconds after addition exceeds [i64::MAX].
/// - Seconds from duration to add exceeds [u32::MAX]. /// - Seconds from duration to add exceeds [u32::MAX].
impl AddAssign<Duration> for UnixTimestamp { impl AddAssign<Duration> for UnixTime {
fn add_assign(&mut self, duration: Duration) { fn add_assign(&mut self, duration: Duration) {
*self = get_new_stamp_after_addition(self, duration); *self = get_new_stamp_after_addition(self, duration);
} }
@ -425,7 +508,7 @@ impl AddAssign<Duration> for UnixTimestamp {
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN]. /// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
/// - Unix seconds after addition exceeds [i64::MAX]. /// - Unix seconds after addition exceeds [i64::MAX].
/// - Unix seconds exceeds [u32::MAX]. /// - Unix seconds exceeds [u32::MAX].
impl Add<Duration> for UnixTimestamp { impl Add<Duration> for UnixTime {
type Output = Self; type Output = Self;
fn add(self, duration: Duration) -> Self::Output { fn add(self, duration: Duration) -> Self::Output {
@ -433,8 +516,8 @@ impl Add<Duration> for UnixTimestamp {
} }
} }
impl Add<Duration> for &UnixTimestamp { impl Add<Duration> for &UnixTime {
type Output = UnixTimestamp; type Output = UnixTime;
fn add(self, duration: Duration) -> Self::Output { fn add(self, duration: Duration) -> Self::Output {
get_new_stamp_after_addition(self, duration) get_new_stamp_after_addition(self, duration)
@ -445,10 +528,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: UnixTime = UnixTime::new(5, 999_999_999);
#[allow(dead_code)]
const UNIX_STAMP_CONST_2: UnixTime = UnixTime::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);
@ -484,28 +572,29 @@ mod tests {
#[test] #[test]
fn basic_unix_stamp_test() { fn basic_unix_stamp_test() {
let stamp = UnixTimestamp::new_only_seconds(-200); let stamp = UnixTime::new_only_secs(-200);
assert_eq!(stamp.unix_seconds, -200); assert_eq!(stamp.secs, -200);
assert_eq!(stamp.subsecond_millis(), 0); assert_eq!(stamp.subsec_millis(), 0);
let stamp = UnixTimestamp::new_only_seconds(250); let stamp = UnixTime::new_only_secs(250);
assert_eq!(stamp.unix_seconds, 250); assert_eq!(stamp.secs, 250);
assert_eq!(stamp.subsecond_millis(), 0); assert_eq!(stamp.subsec_millis(), 0);
} }
#[test] #[test]
fn basic_float_unix_stamp_test() { fn basic_float_unix_stamp_test() {
let stamp = UnixTimestamp::new(500, 600).unwrap(); let stamp = UnixTime::new_subsec_millis_checked(500, 600).unwrap();
assert_eq!(stamp.unix_seconds, 500); assert_eq!(stamp.secs, 500);
let subsec_millis = stamp.subsecond_millis(); let subsec_millis = stamp.subsec_millis();
assert_eq!(subsec_millis, 600); assert_eq!(subsec_millis, 600);
assert!((500.6 - stamp.unix_seconds_f64()).abs() < 0.0001); println!("{:?}", (500.6 - stamp.unix_secs_f64()).to_string());
assert!((500.6 - stamp.unix_secs_f64()).abs() < 0.0001);
} }
#[test] #[test]
fn test_ord_larger() { fn test_ord_larger() {
let stamp0 = UnixTimestamp::new_only_seconds(5); let stamp0 = UnixTime::new_only_secs(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap(); let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6); let stamp2 = UnixTime::new_only_secs(6);
assert!(stamp1 > stamp0); assert!(stamp1 > stamp0);
assert!(stamp2 > stamp0); assert!(stamp2 > stamp0);
assert!(stamp2 > stamp1); assert!(stamp2 > stamp1);
@ -513,9 +602,9 @@ mod tests {
#[test] #[test]
fn test_ord_smaller() { fn test_ord_smaller() {
let stamp0 = UnixTimestamp::new_only_seconds(5); let stamp0 = UnixTime::new_only_secs(5);
let stamp1 = UnixTimestamp::new(5, 500).unwrap(); let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(6); let stamp2 = UnixTime::new_only_secs(6);
assert!(stamp0 < stamp1); assert!(stamp0 < stamp1);
assert!(stamp0 < stamp2); assert!(stamp0 < stamp2);
assert!(stamp1 < stamp2); assert!(stamp1 < stamp2);
@ -523,9 +612,9 @@ 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 = UnixTime::new_only_secs(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6); let stamp2 = UnixTime::new_only_secs(-6);
assert!(stamp0 > stamp1); assert!(stamp0 > stamp1);
assert!(stamp0 > stamp2); assert!(stamp0 > stamp2);
assert!(stamp1 > stamp2); assert!(stamp1 > stamp2);
@ -535,9 +624,9 @@ 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 = UnixTime::new_only_secs(-5);
let stamp1 = UnixTimestamp::new(-5, 500).unwrap(); let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
let stamp2 = UnixTimestamp::new_only_seconds(-6); let stamp2 = UnixTime::new_only_secs(-6);
assert!(stamp2 < stamp1); assert!(stamp2 < stamp1);
assert!(stamp2 < stamp0); assert!(stamp2 < stamp0);
assert!(stamp1 < stamp0); assert!(stamp1 < stamp0);
@ -548,8 +637,8 @@ 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 = UnixTime::new(5, 0);
let stamp1 = UnixTimestamp::new_only_seconds(5); let stamp1 = UnixTime::new_only_secs(5);
assert_eq!(stamp0, stamp1); assert_eq!(stamp0, stamp1);
assert!(stamp0 <= stamp1); assert!(stamp0 <= stamp1);
assert!(stamp0 >= stamp1); assert!(stamp0 >= stamp1);
@ -559,27 +648,27 @@ mod tests {
#[test] #[test]
fn test_addition() { fn test_addition() {
let mut stamp0 = UnixTimestamp::new_only_seconds(1); let mut stamp0 = UnixTime::new_only_secs(1);
stamp0 += Duration::from_secs(5); stamp0 += Duration::from_secs(5);
assert_eq!(stamp0.unix_seconds, 6); assert_eq!(stamp0.secs(), 6);
assert_eq!(stamp0.subsecond_millis(), 0); assert_eq!(stamp0.subsec_millis(), 0);
let stamp1 = stamp0 + Duration::from_millis(500); let stamp1 = stamp0 + Duration::from_millis(500);
assert_eq!(stamp1.unix_seconds, 6); assert_eq!(stamp1.secs, 6);
assert_eq!(stamp1.subsecond_millis(), 500); assert_eq!(stamp1.subsec_millis(), 500);
} }
#[test] #[test]
fn test_addition_on_ref() { fn test_addition_on_ref() {
let stamp0 = &UnixTimestamp::new(20, 500).unwrap(); let stamp0 = &UnixTime::new_subsec_millis_checked(20, 500).unwrap();
let stamp1 = stamp0 + Duration::from_millis(2500); let stamp1 = stamp0 + Duration::from_millis(2500);
assert_eq!(stamp1.unix_seconds, 23); assert_eq!(stamp1.secs, 23);
assert_eq!(stamp1.subsecond_millis(), 0); assert_eq!(stamp1.subsec_millis(), 0);
} }
#[test] #[test]
fn test_as_dt() { fn test_as_dt() {
let stamp = UnixTimestamp::new_only_seconds(0); let stamp = UnixTime::new_only_secs(0);
let dt = stamp.as_date_time().unwrap(); let dt = stamp.chrono_date_time().unwrap();
assert_eq!(dt.year(), 1970); assert_eq!(dt.year(), 1970);
assert_eq!(dt.month(), 1); assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1); assert_eq!(dt.day(), 1);
@ -590,55 +679,55 @@ mod tests {
#[test] #[test]
fn test_from_now() { fn test_from_now() {
let stamp_now = UnixTimestamp::from_now().unwrap(); let stamp_now = UnixTime::from_now().unwrap();
let dt_now = stamp_now.as_date_time().unwrap(); let dt_now = stamp_now.chrono_date_time().unwrap();
assert!(dt_now.year() >= 2020); assert!(dt_now.year() >= 2020);
} }
#[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 = UnixTime::new(2, 0);
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = stamp_later - UnixTimestamp::new(1, 0).unwrap(); } = (stamp_later - UnixTime::new(1, 0)).expect("stamp diff error");
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 = UnixTime::new(3, 800 * 1_000_000);
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = stamp_later - stamp_earlier; } = (stamp_later - stamp_earlier).expect("stamp diff error");
assert!(positive_duration); assert!(positive_duration);
assert_eq!(duration_absolute, Duration::from_millis(1900)); assert_eq!(duration_absolute, Duration::from_millis(1900));
} }
#[test] #[test]
fn test_stamp_diff_negative() { fn test_stamp_diff_negative() {
let stamp_later = UnixTimestamp::new(3, 800).unwrap(); let stamp_later = UnixTime::new_subsec_millis_checked(3, 800).unwrap();
let stamp_earlier = UnixTimestamp::new(1, 900).unwrap(); let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
let StampDiff { let StampDiff {
positive_duration, positive_duration,
duration_absolute, duration_absolute,
} = stamp_earlier - stamp_later; } = (stamp_earlier - stamp_later).expect("stamp diff error");
assert!(!positive_duration); assert!(!positive_duration);
assert_eq!(duration_absolute, Duration::from_millis(1900)); assert_eq!(duration_absolute, Duration::from_millis(1900));
} }
#[test] #[test]
fn test_addition_spillover() { fn test_addition_spillover() {
let mut stamp0 = UnixTimestamp::new(1, 900).unwrap(); let mut stamp0 = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
stamp0 += Duration::from_millis(100); stamp0 += Duration::from_millis(100);
assert_eq!(stamp0.unix_seconds, 2); assert_eq!(stamp0.secs, 2);
assert_eq!(stamp0.subsecond_millis(), 0); assert_eq!(stamp0.subsec_millis(), 0);
stamp0 += Duration::from_millis(1100); stamp0 += Duration::from_millis(1100);
assert_eq!(stamp0.unix_seconds, 3); assert_eq!(stamp0.secs, 3);
assert_eq!(stamp0.subsecond_millis(), 100); assert_eq!(stamp0.subsec_millis(), 100);
} }
#[test] #[test]
@ -647,4 +736,26 @@ mod tests {
let stamp_error = TimestampError::from(cuc_error); let stamp_error = TimestampError::from(cuc_error);
assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}")); assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}"));
} }
#[test]
#[cfg(feature = "timelib")]
fn test_unix_stamp_as_timelib_datetime() {
let stamp_epoch = UnixTime::EPOCH;
let timelib_dt = stamp_epoch.timelib_date_time().unwrap();
assert_eq!(timelib_dt.year(), 1970);
assert_eq!(timelib_dt.month(), time::Month::January);
assert_eq!(timelib_dt.day(), 1);
assert_eq!(timelib_dt.hour(), 0);
assert_eq!(timelib_dt.minute(), 0);
assert_eq!(timelib_dt.second(), 0);
}
#[test]
#[cfg(feature = "timelib")]
fn test_unix_stamp_from_timelib_datetime() {
let timelib_dt = time::OffsetDateTime::UNIX_EPOCH;
let unix_time = UnixTime::from(timelib_dt);
let timelib_converted_back = unix_time.timelib_date_time().unwrap();
assert_eq!(timelib_dt, timelib_converted_back);
}
} }