1
//! CCSDS Time Code Formats according to [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
2
use crate::ByteConversionError;
3
#[cfg(feature = "chrono")]
4
use chrono::{TimeZone, Utc};
5
use core::cmp::Ordering;
6
use core::fmt::{Display, Formatter};
7
use core::ops::{Add, AddAssign, Sub};
8
use core::time::Duration;
9

            
10
#[allow(unused_imports)]
11
#[cfg(not(feature = "std"))]
12
use num_traits::float::FloatCore;
13

            
14
#[cfg(feature = "serde")]
15
use serde::{Deserialize, Serialize};
16

            
17
#[cfg(feature = "std")]
18
use std::error::Error;
19
#[cfg(feature = "std")]
20
use std::time::{SystemTime, SystemTimeError};
21
#[cfg(feature = "std")]
22
pub use std_mod::*;
23

            
24
pub mod ascii;
25
pub mod cds;
26
pub mod cuc;
27

            
28
pub const DAYS_CCSDS_TO_UNIX: i32 = -4383;
29
pub const SECONDS_PER_DAY: u32 = 86400;
30
pub const MS_PER_DAY: u32 = SECONDS_PER_DAY * 1000;
31
pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
32

            
33
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
34
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36
pub enum CcsdsTimeCode {
37
    CucCcsdsEpoch = 0b001,
38
    CucAgencyEpoch = 0b010,
39
    Cds = 0b100,
40
    Ccs = 0b101,
41
    AgencyDefined = 0b110,
42
}
43

            
44
impl TryFrom<u8> for CcsdsTimeCode {
45
    type Error = ();
46

            
47
54
    fn try_from(value: u8) -> Result<Self, Self::Error> {
48
54
        match value {
49
54
            x if x == CcsdsTimeCode::CucCcsdsEpoch as u8 => Ok(CcsdsTimeCode::CucCcsdsEpoch),
50
28
            x if x == CcsdsTimeCode::CucAgencyEpoch as u8 => Ok(CcsdsTimeCode::CucAgencyEpoch),
51
26
            x if x == CcsdsTimeCode::Cds as u8 => Ok(CcsdsTimeCode::Cds),
52
2
            x if x == CcsdsTimeCode::Ccs as u8 => Ok(CcsdsTimeCode::Ccs),
53
2
            x if x == CcsdsTimeCode::AgencyDefined as u8 => Ok(CcsdsTimeCode::AgencyDefined),
54
2
            _ => Err(()),
55
        }
56
54
    }
57
}
58

            
59
/// Retrieve the CCSDS time code from the p-field. If no valid time code identifier is found, the
60
/// value of the raw time code identification field is returned.
61
32
pub fn ccsds_time_code_from_p_field(pfield: u8) -> Result<CcsdsTimeCode, u8> {
62
32
    let raw_bits = (pfield >> 4) & 0b111;
63
32
    CcsdsTimeCode::try_from(raw_bits).map_err(|_| raw_bits)
64
32
}
65

            
66
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
67
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69
pub struct DateBeforeCcsdsEpochError(UnixTime);
70

            
71
impl Display for DateBeforeCcsdsEpochError {
72
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
73
        write!(f, "date before ccsds epoch: {:?}", self.0)
74
    }
75
}
76

            
77
#[cfg(feature = "std")]
78
impl Error for DateBeforeCcsdsEpochError {}
79

            
80
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
81
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
83
#[non_exhaustive]
84
pub enum TimestampError {
85
    InvalidTimeCode { expected: CcsdsTimeCode, found: u8 },
86
    ByteConversion(ByteConversionError),
87
    Cds(cds::CdsError),
88
    Cuc(cuc::CucError),
89
    CustomEpochNotSupported,
90
}
91

            
92
impl Display for TimestampError {
93
16
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
94
16
        match self {
95
2
            TimestampError::InvalidTimeCode { expected, found } => {
96
2
                write!(
97
2
                    f,
98
2
                    "invalid raw time code value {found} for time code {expected:?}"
99
2
                )
100
            }
101
            TimestampError::Cds(e) => {
102
                write!(f, "cds error: {e}")
103
            }
104
2
            TimestampError::Cuc(e) => {
105
2
                write!(f, "cuc error: {e}")
106
            }
107
12
            TimestampError::ByteConversion(e) => {
108
12
                write!(f, "time stamp: {e}")
109
            }
110
            TimestampError::CustomEpochNotSupported => {
111
                write!(f, "custom epochs are not supported")
112
            }
113
        }
114
16
    }
115
}
116

            
117
#[cfg(feature = "std")]
118
impl Error for TimestampError {
119
    fn source(&self) -> Option<&(dyn Error + 'static)> {
120
        match self {
121
            TimestampError::ByteConversion(e) => Some(e),
122
            TimestampError::Cds(e) => Some(e),
123
            TimestampError::Cuc(e) => Some(e),
124
            _ => None,
125
        }
126
    }
127
}
128

            
129
impl From<cds::CdsError> for TimestampError {
130
2
    fn from(e: cds::CdsError) -> Self {
131
2
        TimestampError::Cds(e)
132
2
    }
133
}
134

            
135
impl From<cuc::CucError> for TimestampError {
136
2
    fn from(e: cuc::CucError) -> Self {
137
2
        TimestampError::Cuc(e)
138
2
    }
139
}
140

            
141
#[cfg(feature = "std")]
142
pub mod std_mod {
143
    use crate::time::TimestampError;
144
    use std::time::SystemTimeError;
145
    use thiserror::Error;
146

            
147
    #[derive(Debug, Clone, Error)]
148
    pub enum StdTimestampError {
149
        #[error("system time error: {0:?}")]
150
        SystemTime(#[from] SystemTimeError),
151
        #[error("timestamp error: {0}")]
152
        Timestamp(#[from] TimestampError),
153
    }
154
}
155

            
156
#[cfg(feature = "std")]
157
2
pub fn seconds_since_epoch() -> f64 {
158
2
    SystemTime::now()
159
2
        .duration_since(SystemTime::UNIX_EPOCH)
160
2
        .expect("System time generation failed")
161
2
        .as_secs_f64()
162
2
}
163

            
164
/// Convert UNIX days to CCSDS days
165
///
166
///  - CCSDS epoch: 1958-01-01T00:00:00+00:00
167
///  - UNIX Epoch: 1970-01-01T00:00:00+00:00
168
#[inline]
169
86
pub const fn unix_to_ccsds_days(unix_days: i64) -> i64 {
170
86
    unix_days - DAYS_CCSDS_TO_UNIX as i64
171
86
}
172

            
173
/// Convert CCSDS days to UNIX days
174
///
175
///  - CCSDS epoch: 1958-01-01T00:00:00+00:00
176
///  - UNIX Epoch: 1970-01-01T00:00:00+00:00
177
#[inline]
178
118
pub const fn ccsds_to_unix_days(ccsds_days: i64) -> i64 {
179
118
    ccsds_days + DAYS_CCSDS_TO_UNIX as i64
180
118
}
181

            
182
/// Similar to [unix_to_ccsds_days] but converts the epoch instead, which is the number of elpased
183
/// seconds since the CCSDS and UNIX epoch times.
184
#[inline]
185
8
pub const fn unix_epoch_to_ccsds_epoch(unix_epoch: i64) -> i64 {
186
8
    unix_epoch - (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
187
8
}
188

            
189
#[inline]
190
4
pub const fn ccsds_epoch_to_unix_epoch(ccsds_epoch: i64) -> i64 {
191
4
    ccsds_epoch + (DAYS_CCSDS_TO_UNIX as i64 * SECONDS_PER_DAY as i64)
192
4
}
193

            
194
#[cfg(feature = "std")]
195
pub fn ms_of_day_using_sysclock() -> u32 {
196
    ms_of_day(seconds_since_epoch())
197
}
198

            
199
4
pub fn ms_of_day(seconds_since_epoch: f64) -> u32 {
200
4
    let fraction_ms = seconds_since_epoch - seconds_since_epoch.floor();
201
4
    let ms_of_day: u32 = (((seconds_since_epoch.floor() as u32 % SECONDS_PER_DAY) * 1000) as f64
202
4
        + fraction_ms)
203
4
        .floor() as u32;
204
4
    ms_of_day
205
4
}
206

            
207
pub trait TimeWriter {
208
    fn len_written(&self) -> usize;
209

            
210
    /// Generic function to convert write a timestamp into a raw buffer.
211
    /// Returns the number of written bytes on success.
212
    fn write_to_bytes(&self, bytes: &mut [u8]) -> Result<usize, TimestampError>;
213

            
214
    #[cfg(feature = "alloc")]
215
6
    fn to_vec(&self) -> Result<alloc::vec::Vec<u8>, TimestampError> {
216
6
        let mut vec = alloc::vec![0; self.len_written()];
217
6
        self.write_to_bytes(&mut vec)?;
218
6
        Ok(vec)
219
6
    }
220
}
221

            
222
pub trait TimeReader: Sized {
223
    fn from_bytes(buf: &[u8]) -> Result<Self, TimestampError>;
224
}
225

            
226
/// Trait for generic CCSDS time providers.
227
///
228
/// The UNIX helper methods and the helper method are not strictly necessary but extremely
229
/// practical because they are a very common and simple exchange format for time information.
230
/// Therefore, it was decided to keep them in this trait as well.
231
pub trait CcsdsTimeProvider {
232
    fn len_as_bytes(&self) -> usize;
233

            
234
    /// Returns the pfield of the time provider. The pfield can have one or two bytes depending
235
    /// on the extension bit (first bit). The time provider should returns a tuple where the first
236
    /// entry denotes the length of the pfield and the second entry is the value of the pfield
237
    /// in big endian format.
238
    fn p_field(&self) -> (usize, [u8; 2]);
239
    fn ccdsd_time_code(&self) -> CcsdsTimeCode;
240

            
241
    fn unix_secs(&self) -> i64;
242
    fn subsec_nanos(&self) -> u32;
243

            
244
4
    fn subsec_millis(&self) -> u16 {
245
4
        (self.subsec_nanos() / 1_000_000) as u16
246
4
    }
247

            
248
    fn unix_time(&self) -> UnixTime {
249
        UnixTime::new(self.unix_secs(), self.subsec_nanos())
250
    }
251

            
252
    #[cfg(feature = "chrono")]
253
32
    fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
254
32
        chrono::Utc.timestamp_opt(self.unix_secs(), self.subsec_nanos())
255
32
    }
256

            
257
    #[cfg(feature = "timelib")]
258
2
    fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
259
2
        Ok(time::OffsetDateTime::from_unix_timestamp(self.unix_secs())?
260
2
            + time::Duration::nanoseconds(self.subsec_nanos().into()))
261
2
    }
262
}
263

            
264
/// UNIX time: Elapsed non-leap seconds since 1970-01-01T00:00:00+00:00 UTC.
265
///
266
/// This is a commonly used time format and can therefore also be used as a generic format to
267
/// convert other CCSDS time formats to and from. The subsecond precision is in nanoseconds
268
/// similarly to other common time formats and libraries.
269
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
270
2
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
271
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
272
pub struct UnixTime {
273
    secs: i64,
274
    subsec_nanos: u32,
275
}
276

            
277
impl UnixTime {
278
    /// The UNIX epoch time: 1970-01-01T00:00:00+00:00 UTC.
279
    pub const EPOCH: Self = Self {
280
        secs: 0,
281
        subsec_nanos: 0,
282
    };
283

            
284
    /// The minimum possible `UnixTime`.
285
    pub const MIN: Self = Self {
286
        secs: i64::MIN,
287
        subsec_nanos: 0,
288
    };
289

            
290
    /// The maximum possible `UnixTime`.
291
    pub const MAX: Self = Self {
292
        secs: i64::MAX,
293
        subsec_nanos: NANOS_PER_SECOND - 1,
294
    };
295

            
296
    /// Returns [None] if the subsecond nanosecond value is invalid (larger than fraction of a
297
    /// second)
298
22
    pub fn new_checked(unix_seconds: i64, subsec_nanos: u32) -> Option<Self> {
299
22
        if subsec_nanos >= NANOS_PER_SECOND {
300
            return None;
301
22
        }
302
22
        Some(Self::new(unix_seconds, subsec_nanos))
303
22
    }
304

            
305
    /// Returns [None] if the subsecond millisecond value is invalid (larger than fraction of a
306
    /// second)
307
20
    pub fn new_subsec_millis_checked(unix_seconds: i64, subsec_millis: u16) -> Option<Self> {
308
20
        if subsec_millis >= 1000 {
309
            return None;
310
20
        }
311
20
        Self::new_checked(unix_seconds, subsec_millis as u32 * 1_000_000)
312
20
    }
313

            
314
    /// This function will panic if the subsecond value is larger than the fraction of a second.
315
    /// Use [Self::new_checked] if you want to handle this case without a panic.
316
216
    pub const fn new(unix_seconds: i64, subsecond_nanos: u32) -> Self {
317
216
        if subsecond_nanos >= NANOS_PER_SECOND {
318
            panic!("invalid subsecond nanos value");
319
216
        }
320
216
        Self {
321
216
            secs: unix_seconds,
322
216
            subsec_nanos: subsecond_nanos,
323
216
        }
324
216
    }
325

            
326
    /// This function will panic if the subsecond value is larger than the fraction of a second.
327
    /// Use [Self::new_subsec_millis_checked] if you want to handle this case without a panic.
328
    pub const fn new_subsec_millis(unix_seconds: i64, subsecond_millis: u16) -> Self {
329
        if subsecond_millis >= 1000 {
330
            panic!("invalid subsecond millisecond value");
331
        }
332
        Self {
333
            secs: unix_seconds,
334
            subsec_nanos: subsecond_millis as u32 * 1_000_000,
335
        }
336
    }
337

            
338
26
    pub fn new_only_secs(unix_seconds: i64) -> Self {
339
26
        Self {
340
26
            secs: unix_seconds,
341
26
            subsec_nanos: 0,
342
26
        }
343
26
    }
344

            
345
    #[inline]
346
42
    pub fn subsec_millis(&self) -> u16 {
347
42
        (self.subsec_nanos / 1_000_000) as u16
348
42
    }
349

            
350
14
    pub fn subsec_nanos(&self) -> u32 {
351
14
        self.subsec_nanos
352
14
    }
353

            
354
    #[cfg(feature = "std")]
355
2
    pub fn now() -> Result<Self, SystemTimeError> {
356
2
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
357
2
        let epoch = now.as_secs();
358
2
        Ok(Self::new(epoch as i64, now.subsec_nanos()))
359
2
    }
360

            
361
    #[inline]
362
4
    pub fn unix_secs_f64(&self) -> f64 {
363
4
        self.secs as f64 + (self.subsec_nanos as f64 / 1_000_000_000.0)
364
4
    }
365

            
366
6
    pub fn as_secs(&self) -> i64 {
367
6
        self.secs
368
6
    }
369

            
370
    #[cfg(feature = "chrono")]
371
8
    pub fn chrono_date_time(&self) -> chrono::LocalResult<chrono::DateTime<chrono::Utc>> {
372
8
        Utc.timestamp_opt(self.secs, self.subsec_nanos)
373
8
    }
374

            
375
    #[cfg(feature = "timelib")]
376
4
    pub fn timelib_date_time(&self) -> Result<time::OffsetDateTime, time::error::ComponentRange> {
377
4
        Ok(time::OffsetDateTime::from_unix_timestamp(self.as_secs())?
378
4
            + time::Duration::nanoseconds(self.subsec_nanos().into()))
379
4
    }
380

            
381
    // Calculate the difference in milliseconds between two UnixTimestamps
382
6
    pub fn diff_in_millis(&self, other: &UnixTime) -> Option<i64> {
383
6
        let seconds_difference = self.secs.checked_sub(other.secs)?;
384
        // Convert seconds difference to milliseconds
385
6
        let milliseconds_difference = seconds_difference.checked_mul(1000)?;
386

            
387
        // Calculate the difference in subsecond milliseconds directly
388
6
        let subsecond_difference_nanos = self.subsec_nanos as i64 - other.subsec_nanos as i64;
389
6

            
390
6
        // Combine the differences
391
6
        Some(milliseconds_difference + (subsecond_difference_nanos / 1_000_000))
392
6
    }
393
}
394

            
395
#[cfg(feature = "chrono")]
396
impl From<chrono::DateTime<chrono::Utc>> for UnixTime {
397
6
    fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
398
6
        Self::new(value.timestamp(), value.timestamp_subsec_nanos())
399
6
    }
400
}
401

            
402
#[cfg(feature = "timelib")]
403
impl From<time::OffsetDateTime> for UnixTime {
404
2
    fn from(value: time::OffsetDateTime) -> Self {
405
2
        Self::new(value.unix_timestamp(), value.nanosecond())
406
2
    }
407
}
408

            
409
impl PartialOrd for UnixTime {
410
40
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
411
40
        Some(self.cmp(other))
412
40
    }
413
}
414

            
415
impl Ord for UnixTime {
416
40
    fn cmp(&self, other: &Self) -> Ordering {
417
40
        if self == other {
418
8
            return Ordering::Equal;
419
32
        }
420
32
        match self.secs.cmp(&other.secs) {
421
10
            Ordering::Less => return Ordering::Less,
422
10
            Ordering::Greater => return Ordering::Greater,
423
12
            _ => (),
424
12
        }
425
12

            
426
12
        match self.subsec_millis().cmp(&other.subsec_millis()) {
427
            Ordering::Less => {
428
6
                return if self.secs < 0 {
429
4
                    Ordering::Greater
430
                } else {
431
2
                    Ordering::Less
432
                }
433
            }
434
            Ordering::Greater => {
435
6
                return if self.secs < 0 {
436
4
                    Ordering::Less
437
                } else {
438
2
                    Ordering::Greater
439
                }
440
            }
441
            Ordering::Equal => (),
442
        }
443
        Ordering::Equal
444
40
    }
445
}
446

            
447
/// Difference between two UNIX timestamps. The [Duration] type can not contain negative durations,
448
/// so the sign information is supplied separately.
449
#[derive(Clone, Copy, PartialEq, Eq)]
450
pub struct StampDiff {
451
    pub positive_duration: bool,
452
    pub duration_absolute: Duration,
453
}
454

            
455
impl Sub for UnixTime {
456
    type Output = Option<StampDiff>;
457

            
458
6
    fn sub(self, rhs: Self) -> Self::Output {
459
6
        let difference = self.diff_in_millis(&rhs)?;
460
6
        Some(if difference < 0 {
461
2
            StampDiff {
462
2
                positive_duration: false,
463
2
                duration_absolute: Duration::from_millis(-difference as u64),
464
2
            }
465
        } else {
466
4
            StampDiff {
467
4
                positive_duration: true,
468
4
                duration_absolute: Duration::from_millis(difference as u64),
469
4
            }
470
        })
471
6
    }
472
}
473

            
474
10
fn get_new_stamp_after_addition(current_stamp: &UnixTime, duration: Duration) -> UnixTime {
475
10
    let mut new_subsec_nanos = current_stamp.subsec_nanos() + duration.subsec_nanos();
476
10
    let mut new_unix_seconds = current_stamp.secs;
477
19
    let mut increment_seconds = |value: u32| {
478
14
        if new_unix_seconds < 0 {
479
            new_unix_seconds = new_unix_seconds
480
                .checked_sub(value.into())
481
                .expect("new unix seconds would exceed i64::MIN");
482
14
        } else {
483
14
            new_unix_seconds = new_unix_seconds
484
14
                .checked_add(value.into())
485
14
                .expect("new unix seconds would exceed i64::MAX");
486
14
        }
487
14
    };
488
10
    if new_subsec_nanos >= 1_000_000_000 {
489
4
        new_subsec_nanos -= 1_000_000_000;
490
4
        increment_seconds(1);
491
6
    }
492
10
    increment_seconds(
493
10
        duration
494
10
            .as_secs()
495
10
            .try_into()
496
10
            .expect("duration seconds exceeds u32::MAX"),
497
10
    );
498
10
    UnixTime::new(new_unix_seconds, new_subsec_nanos)
499
10
}
500

            
501
/// Please note that this operation will panic on the following conditions:
502
///
503
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
504
/// - Unix seconds after addition  exceeds [i64::MAX].
505
/// - Seconds from duration to add exceeds [u32::MAX].
506
impl AddAssign<Duration> for UnixTime {
507
6
    fn add_assign(&mut self, duration: Duration) {
508
6
        *self = get_new_stamp_after_addition(self, duration);
509
6
    }
510
}
511

            
512
/// Please note that this operation will panic for the following conditions:
513
///
514
/// - Unix seconds after subtraction for stamps before the unix epoch exceeds [i64::MIN].
515
/// - Unix seconds after addition  exceeds [i64::MAX].
516
/// - Unix seconds exceeds [u32::MAX].
517
impl Add<Duration> for UnixTime {
518
    type Output = Self;
519

            
520
2
    fn add(self, duration: Duration) -> Self::Output {
521
2
        get_new_stamp_after_addition(&self, duration)
522
2
    }
523
}
524

            
525
impl Add<Duration> for &UnixTime {
526
    type Output = UnixTime;
527

            
528
2
    fn add(self, duration: Duration) -> Self::Output {
529
2
        get_new_stamp_after_addition(self, duration)
530
2
    }
531
}
532

            
533
#[cfg(all(test, feature = "std"))]
534
mod tests {
535
    use alloc::string::ToString;
536
    use chrono::{Datelike, Timelike};
537
    use std::{format, println};
538

            
539
    use super::{cuc::CucError, *};
540

            
541
    #[allow(dead_code)]
542
    const UNIX_STAMP_CONST: UnixTime = UnixTime::new(5, 999_999_999);
543
    #[allow(dead_code)]
544
    const UNIX_STAMP_CONST_2: UnixTime = UnixTime::new_subsec_millis(5, 999);
545

            
546
    #[test]
547
2
    fn test_days_conversion() {
548
2
        assert_eq!(unix_to_ccsds_days(DAYS_CCSDS_TO_UNIX.into()), 0);
549
2
        assert_eq!(ccsds_to_unix_days(0), DAYS_CCSDS_TO_UNIX.into());
550
2
    }
551

            
552
    #[test]
553
    #[cfg_attr(miri, ignore)]
554
2
    fn test_get_current_time() {
555
2
        let sec_floats = seconds_since_epoch();
556
2
        assert!(sec_floats > 0.0);
557
2
    }
558

            
559
    #[test]
560
2
    fn test_ms_of_day() {
561
2
        let ms = ms_of_day(0.0);
562
2
        assert_eq!(ms, 0);
563
2
        let ms = ms_of_day(5.0);
564
2
        assert_eq!(ms, 5000);
565
2
    }
566

            
567
    #[test]
568
    #[cfg_attr(miri, ignore)]
569
2
    fn test_ccsds_epoch() {
570
2
        let now = SystemTime::now()
571
2
            .duration_since(SystemTime::UNIX_EPOCH)
572
2
            .unwrap();
573
2
        let unix_epoch = now.as_secs();
574
2
        let ccsds_epoch = unix_epoch_to_ccsds_epoch(now.as_secs() as i64) as u64;
575
2
        assert!(ccsds_epoch > unix_epoch);
576
2
        assert_eq!((ccsds_epoch - unix_epoch) % SECONDS_PER_DAY as u64, 0);
577
2
        let days_diff = (ccsds_epoch - unix_epoch) / SECONDS_PER_DAY as u64;
578
2
        assert_eq!(days_diff, -DAYS_CCSDS_TO_UNIX as u64);
579
2
    }
580

            
581
    #[test]
582
2
    fn basic_unix_stamp_test() {
583
2
        let stamp = UnixTime::new_only_secs(-200);
584
2
        assert_eq!(stamp.secs, -200);
585
2
        assert_eq!(stamp.subsec_millis(), 0);
586
2
        let stamp = UnixTime::new_only_secs(250);
587
2
        assert_eq!(stamp.secs, 250);
588
2
        assert_eq!(stamp.subsec_millis(), 0);
589
2
    }
590

            
591
    #[test]
592
2
    fn basic_float_unix_stamp_test() {
593
2
        let stamp = UnixTime::new_subsec_millis_checked(500, 600).unwrap();
594
2
        assert_eq!(stamp.secs, 500);
595
2
        let subsec_millis = stamp.subsec_millis();
596
2
        assert_eq!(subsec_millis, 600);
597
2
        println!("{:?}", (500.6 - stamp.unix_secs_f64()).to_string());
598
2
        assert!((500.6 - stamp.unix_secs_f64()).abs() < 0.0001);
599
2
    }
600

            
601
    #[test]
602
2
    fn test_ord_larger() {
603
2
        let stamp0 = UnixTime::new_only_secs(5);
604
2
        let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
605
2
        let stamp2 = UnixTime::new_only_secs(6);
606
2
        assert!(stamp1 > stamp0);
607
2
        assert!(stamp2 > stamp0);
608
2
        assert!(stamp2 > stamp1);
609
2
    }
610

            
611
    #[test]
612
2
    fn test_ord_smaller() {
613
2
        let stamp0 = UnixTime::new_only_secs(5);
614
2
        let stamp1 = UnixTime::new_subsec_millis_checked(5, 500).unwrap();
615
2
        let stamp2 = UnixTime::new_only_secs(6);
616
2
        assert!(stamp0 < stamp1);
617
2
        assert!(stamp0 < stamp2);
618
2
        assert!(stamp1 < stamp2);
619
2
    }
620

            
621
    #[test]
622
2
    fn test_ord_larger_neg_numbers() {
623
2
        let stamp0 = UnixTime::new_only_secs(-5);
624
2
        let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
625
2
        let stamp2 = UnixTime::new_only_secs(-6);
626
2
        assert!(stamp0 > stamp1);
627
2
        assert!(stamp0 > stamp2);
628
2
        assert!(stamp1 > stamp2);
629
2
        assert!(stamp1 >= stamp2);
630
2
        assert!(stamp0 >= stamp1);
631
2
    }
632

            
633
    #[test]
634
2
    fn test_ord_smaller_neg_numbers() {
635
2
        let stamp0 = UnixTime::new_only_secs(-5);
636
2
        let stamp1 = UnixTime::new_subsec_millis_checked(-5, 500).unwrap();
637
2
        let stamp2 = UnixTime::new_only_secs(-6);
638
2
        assert!(stamp2 < stamp1);
639
2
        assert!(stamp2 < stamp0);
640
2
        assert!(stamp1 < stamp0);
641
2
        assert!(stamp1 <= stamp0);
642
2
        assert!(stamp2 <= stamp1);
643
2
    }
644

            
645
    #[allow(clippy::nonminimal_bool)]
646
    #[test]
647
2
    fn test_eq() {
648
2
        let stamp0 = UnixTime::new(5, 0);
649
2
        let stamp1 = UnixTime::new_only_secs(5);
650
2
        assert_eq!(stamp0, stamp1);
651
2
        assert!(stamp0 <= stamp1);
652
2
        assert!(stamp0 >= stamp1);
653
2
        assert!(!(stamp0 < stamp1));
654
2
        assert!(!(stamp0 > stamp1));
655
2
    }
656

            
657
    #[test]
658
2
    fn test_addition() {
659
2
        let mut stamp0 = UnixTime::new_only_secs(1);
660
2
        stamp0 += Duration::from_secs(5);
661
2
        assert_eq!(stamp0.as_secs(), 6);
662
2
        assert_eq!(stamp0.subsec_millis(), 0);
663
2
        let stamp1 = stamp0 + Duration::from_millis(500);
664
2
        assert_eq!(stamp1.secs, 6);
665
2
        assert_eq!(stamp1.subsec_millis(), 500);
666
2
    }
667

            
668
    #[test]
669
2
    fn test_addition_on_ref() {
670
2
        let stamp0 = &UnixTime::new_subsec_millis_checked(20, 500).unwrap();
671
2
        let stamp1 = stamp0 + Duration::from_millis(2500);
672
2
        assert_eq!(stamp1.secs, 23);
673
2
        assert_eq!(stamp1.subsec_millis(), 0);
674
2
    }
675

            
676
    #[test]
677
2
    fn test_as_dt() {
678
2
        let stamp = UnixTime::new_only_secs(0);
679
2
        let dt = stamp.chrono_date_time().unwrap();
680
2
        assert_eq!(dt.year(), 1970);
681
2
        assert_eq!(dt.month(), 1);
682
2
        assert_eq!(dt.day(), 1);
683
2
        assert_eq!(dt.hour(), 0);
684
2
        assert_eq!(dt.minute(), 0);
685
2
        assert_eq!(dt.second(), 0);
686
2
    }
687

            
688
    #[test]
689
    #[cfg_attr(miri, ignore)]
690
2
    fn test_from_now() {
691
2
        let stamp_now = UnixTime::now().unwrap();
692
2
        let dt_now = stamp_now.chrono_date_time().unwrap();
693
2
        assert!(dt_now.year() >= 2020);
694
2
    }
695

            
696
    #[test]
697
2
    fn test_stamp_diff_positive_0() {
698
2
        let stamp_later = UnixTime::new(2, 0);
699
2
        let StampDiff {
700
2
            positive_duration,
701
2
            duration_absolute,
702
2
        } = (stamp_later - UnixTime::new(1, 0)).expect("stamp diff error");
703
2
        assert!(positive_duration);
704
2
        assert_eq!(duration_absolute, Duration::from_secs(1));
705
2
    }
706

            
707
    #[test]
708
2
    fn test_stamp_diff_positive_1() {
709
2
        let stamp_later = UnixTime::new(3, 800 * 1_000_000);
710
2
        let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
711
2
        let StampDiff {
712
2
            positive_duration,
713
2
            duration_absolute,
714
2
        } = (stamp_later - stamp_earlier).expect("stamp diff error");
715
2
        assert!(positive_duration);
716
2
        assert_eq!(duration_absolute, Duration::from_millis(1900));
717
2
    }
718

            
719
    #[test]
720
2
    fn test_stamp_diff_negative() {
721
2
        let stamp_later = UnixTime::new_subsec_millis_checked(3, 800).unwrap();
722
2
        let stamp_earlier = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
723
2
        let StampDiff {
724
2
            positive_duration,
725
2
            duration_absolute,
726
2
        } = (stamp_earlier - stamp_later).expect("stamp diff error");
727
2
        assert!(!positive_duration);
728
2
        assert_eq!(duration_absolute, Duration::from_millis(1900));
729
2
    }
730

            
731
    #[test]
732
2
    fn test_addition_spillover() {
733
2
        let mut stamp0 = UnixTime::new_subsec_millis_checked(1, 900).unwrap();
734
2
        stamp0 += Duration::from_millis(100);
735
2
        assert_eq!(stamp0.secs, 2);
736
2
        assert_eq!(stamp0.subsec_millis(), 0);
737
2
        stamp0 += Duration::from_millis(1100);
738
2
        assert_eq!(stamp0.secs, 3);
739
2
        assert_eq!(stamp0.subsec_millis(), 100);
740
2
    }
741

            
742
    #[test]
743
2
    fn test_cuc_error_printout() {
744
2
        let cuc_error = CucError::InvalidCounterWidth(12);
745
2
        let stamp_error = TimestampError::from(cuc_error);
746
2
        assert_eq!(stamp_error.to_string(), format!("cuc error: {cuc_error}"));
747
2
    }
748

            
749
    #[test]
750
    #[cfg(feature = "timelib")]
751
2
    fn test_unix_stamp_as_timelib_datetime() {
752
2
        let stamp_epoch = UnixTime::EPOCH;
753
2
        let timelib_dt = stamp_epoch.timelib_date_time().unwrap();
754
2
        assert_eq!(timelib_dt.year(), 1970);
755
2
        assert_eq!(timelib_dt.month(), time::Month::January);
756
2
        assert_eq!(timelib_dt.day(), 1);
757
2
        assert_eq!(timelib_dt.hour(), 0);
758
2
        assert_eq!(timelib_dt.minute(), 0);
759
2
        assert_eq!(timelib_dt.second(), 0);
760
2
    }
761

            
762
    #[test]
763
    #[cfg(feature = "timelib")]
764
2
    fn test_unix_stamp_from_timelib_datetime() {
765
2
        let timelib_dt = time::OffsetDateTime::UNIX_EPOCH;
766
2
        let unix_time = UnixTime::from(timelib_dt);
767
2
        let timelib_converted_back = unix_time.timelib_date_time().unwrap();
768
2
        assert_eq!(timelib_dt, timelib_converted_back);
769
2
    }
770
}