zencan_common/
time_types.rs

1//! Data types for TimeOfDay and TimeDifference fields
2
3use chrono::{Datelike, NaiveDate, NaiveTime, TimeDelta, Timelike};
4use core::time::Duration;
5use snafu::Snafu;
6
7const MILLIS_PER_DAY: u64 = 86_400_000;
8
9#[derive(Clone, Copy, Debug, Snafu)]
10pub enum TimeCreateError {
11    /// The provided time is before the epoch and cannot be represented
12    PreEpoch,
13    /// The provided is too far into the future to be represented by TimeOfDay
14    OutOfRange,
15    /// The provided date is invalid
16    ///
17    /// This likely means that the date you specified does not exist or is outside the range which
18    /// can be be represented by chrono::NaiveDate
19    InvalidDate,
20}
21
22/// Represents a time in 48-bits, as stored in TimeOfDay objects
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub struct TimeOfDay(TimeDifference);
25
26impl TimeOfDay {
27    /// The size of a TimeOfDay object in bytes as stored in the object dict
28    pub const SIZE: usize = 6;
29
30    /// A zero-inintialized TimeOfDay corresponding to the TimeOfDay epoch of 1984-01-01
31    pub const EPOCH: TimeOfDay = TimeOfDay(TimeDifference { ms: 0, days: 0 });
32    // TimeOfDay objects are encoded with reference to 1984-01-01
33    const CHRONO_EPOCH: NaiveDate = NaiveDate::from_ymd_opt(1984, 1, 1).unwrap();
34
35    /// Create a new TimeOfDay
36    ///
37    /// # Arguments
38    /// - `days`: The number of days since January 1, 1984
39    /// - `ms`: The number of milliseconds after midnight
40    pub fn new(days: u16, ms: u32) -> Self {
41        Self(TimeDifference::new(days, ms))
42    }
43
44    /// Create a TimeOfDay corresponding to the provided date and time
45    pub fn from_ymd_hms_ms(
46        year: u32,
47        month: u32,
48        day: u32,
49        hour: u32,
50        min: u32,
51        sec: u32,
52        milli: u32,
53    ) -> Result<Self, TimeCreateError> {
54        let chrono_date = NaiveDate::from_ymd_opt(year as i32, month, day)
55            .ok_or(InvalidDateSnafu.build())?
56            .and_hms_milli_opt(hour, min, sec, milli)
57            .ok_or(InvalidDateSnafu.build())?;
58
59        let delta = chrono_date - const { Self::CHRONO_EPOCH.and_hms_opt(0, 0, 0).unwrap() };
60        let days = delta.num_days();
61        let ms = (delta - TimeDelta::days(days)).num_milliseconds();
62        if days < 0 {
63            PreEpochSnafu.fail()
64        } else if days > u16::MAX as i64 {
65            OutOfRangeSnafu.fail()
66        } else {
67            Ok(Self::new(days as u16, ms as u32))
68        }
69    }
70
71    /// Create a TimeOfDay from little endian bytes
72    pub fn from_le_bytes(bytes: [u8; 6]) -> Self {
73        Self(TimeDifference::from_le_bytes(bytes))
74    }
75
76    /// Get the little endian byte representation of the time of day
77    pub fn to_le_bytes(&self) -> [u8; 6] {
78        self.0.to_le_bytes()
79    }
80
81    /// Get the date represented
82    ///
83    /// Returns (year, month, day)
84    pub fn date_ymd(&self) -> (u32, u32, u32) {
85        let date = Self::CHRONO_EPOCH + self.0.as_chrono_delta();
86        (date.year() as u32, date.month(), date.day())
87    }
88
89    /// Get the date as number of days since 1984-01-01
90    pub fn days(&self) -> u16 {
91        self.0.days
92    }
93
94    /// Get the time of day as (hour, min, sec, millis)
95    pub fn time_hmsm(&self) -> (u32, u32, u32, u32) {
96        let sec = self.0.ms / 1000;
97        let nanos = (self.0.ms % 1000) * 1000;
98        let t = NaiveTime::from_num_seconds_from_midnight_opt(sec, nanos).unwrap();
99        (t.hour(), t.minute(), t.second(), t.nanosecond() / 1000)
100    }
101
102    /// Get the time of day as the number of milliseconds since midnight
103    pub fn time_millis(&self) -> u32 {
104        self.0.ms
105    }
106
107    /// Get the time of of day as a [`Duration`] since midnight
108    pub fn time_duration(&self) -> Duration {
109        Duration::from_millis(self.time_millis() as u64)
110    }
111
112    /// Get the total number of milliseconds since 1984-01-01
113    pub fn total_millis(&self) -> u64 {
114        self.0.total_millis()
115    }
116
117    /// Get the time represented as a SystemTime
118    #[cfg(feature = "std")]
119    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
120    pub fn as_system_time(&self) -> std::time::SystemTime {
121        use std::time::SystemTime;
122
123        // System time is relative to the UNIX_EPOCH of 1970-01-01
124        const UNIX_EPOCH: NaiveDate = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
125        let epoch_delta_millis = (Self::CHRONO_EPOCH - UNIX_EPOCH).num_milliseconds() as u64;
126        SystemTime::UNIX_EPOCH + self.0.as_duration() + Duration::from_millis(epoch_delta_millis)
127    }
128}
129
130/// Represents a duration of time in 48-bits, as stored in TimeDifference objects
131#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
132pub struct TimeDifference {
133    ms: u32,
134    days: u16,
135}
136
137impl TimeDifference {
138    /// The size of a TimeDifference object in bytes as stored in the object dict
139    pub const SIZE: usize = 6;
140
141    /// A zero time difference
142    pub const ZERO: TimeDifference = TimeDifference { ms: 0, days: 0 };
143
144    /// Create a new time difference from the raw u32 value
145    pub const fn new(days: u16, ms: u32) -> Self {
146        Self { ms, days }
147    }
148
149    /// Create a TimeOfDay from little endian bytes
150    pub fn from_le_bytes(bytes: [u8; 6]) -> Self {
151        let ms = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
152        let days = u16::from_le_bytes(bytes[4..6].try_into().unwrap());
153        Self::new(days, ms)
154    }
155
156    /// Return the little endian byte representation of the TimeDifference
157    pub fn to_le_bytes(&self) -> [u8; 6] {
158        let mut bytes = [0; 6];
159        bytes[0..4].copy_from_slice(&self.ms.to_le_bytes());
160        bytes[4..6].copy_from_slice(&self.days.to_le_bytes());
161        bytes
162    }
163
164    /// Get the time duration as milliseconds
165    pub fn total_millis(&self) -> u64 {
166        self.days as u64 * MILLIS_PER_DAY + self.ms as u64
167    }
168
169    /// Convert to a [`core::time::Duration`]
170    pub fn as_duration(&self) -> Duration {
171        Duration::from_millis(self.days as u64 * MILLIS_PER_DAY + self.ms as u64)
172    }
173
174    pub(crate) fn as_chrono_delta(&self) -> chrono::TimeDelta {
175        chrono::TimeDelta::milliseconds(self.days as i64 * MILLIS_PER_DAY as i64 + self.ms as i64)
176    }
177}