labview_interop/types/
timestamp.rs

1//! Support for exchanging date and times. with LabVIEWs timestamp format.
2//!
3//! This includes binary formats, to and from 1904 epoch, unix (1970) epoch
4//! and optionally chrono DateTime with the `chrono` feature.
5//!
6
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum LVTimeError {
11    #[error("Cannot generate a chrono time as it is out of range.")]
12    ChronoOutOfRange,
13}
14
15/// Mirrors the internal LabVIEW timestamp structure so
16/// it can be passed back and forward.
17#[repr(C)]
18#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
19pub struct LVTime {
20    fractions: u64,
21    seconds: i64,
22}
23
24///The Unix Epoch in LabVIEW epoch seconds for shifting timestamps between them.
25///
26/// This is the [`i64`] value. See also [`UNIX_EPOCH_IN_LV_SECONDS_F64`].
27pub const UNIX_EPOCH_IN_LV_SECONDS_I64: i64 = 2082844800;
28
29///The Unix Epoch in LabVIEW epoch seconds for shifting timestamps between them.
30///
31/// This is the [`f64`] value. See also [`UNIX_EPOCH_IN_LV_SECONDS_I64`].
32pub const UNIX_EPOCH_IN_LV_SECONDS_F64: f64 = UNIX_EPOCH_IN_LV_SECONDS_I64 as f64;
33
34impl LVTime {
35    /// Extract the sub-second component as a floating point number.
36    pub fn sub_seconds(&self) -> f64 {
37        let fractional = self.to_parts().1;
38        (fractional as f64) / 0xFFFF_FFFF_FFFF_FFFFu64 as f64
39    }
40
41    ///Extract the seconds component which is referenced to the LabVIEW epoch.
42    #[inline]
43    pub const fn seconds(&self) -> i64 {
44        self.seconds
45    }
46
47    /// From a double precision number which is the seconds
48    /// since the 1904 epoch used by LabVIEW
49    pub fn from_lv_epoch(seconds: f64) -> Self {
50        let (seconds, fractions) = (seconds / 1.0, seconds % 1.0);
51        let integer_fractions = (fractions * 0xFFFF_FFFF_FFFF_FFFFu64 as f64) as u64;
52        Self::from_parts(seconds as i64, integer_fractions)
53    }
54
55    /// Into a double precision number which is the seconds
56    /// since the 1904 epoch used by LabVIEW.
57    pub fn to_lv_epoch(&self) -> f64 {
58        self.seconds() as f64 + self.sub_seconds()
59    }
60
61    /// To a double precision number which is the seconds since unix epoch.
62    pub fn to_unix_epoch(&self) -> f64 {
63        let lv_epoch = self.to_lv_epoch();
64        lv_epoch - UNIX_EPOCH_IN_LV_SECONDS_F64
65    }
66
67    /// To a double precision number which is the seconds since unix epoch.
68    pub fn from_unix_epoch(seconds: f64) -> Self {
69        let lv_epoch = seconds + UNIX_EPOCH_IN_LV_SECONDS_F64;
70        Self::from_lv_epoch(lv_epoch)
71    }
72
73    /// Build from the full seconds and fractional second parts.
74    pub const fn from_parts(seconds: i64, fractions: u64) -> Self {
75        Self { seconds, fractions }
76    }
77
78    /// Seperate out the u64 components.
79    #[inline]
80    pub const fn to_parts(&self) -> (i64, u64) {
81        (self.seconds, self.fractions)
82    }
83
84    /// To little endian bytes.
85    pub const fn to_le_bytes(&self) -> [u8; 16] {
86        // Note the reversal here so it is like a u128.
87        let littlest = self.fractions.to_le_bytes();
88        let biggest = self.seconds.to_le_bytes();
89        [
90            littlest[0],
91            littlest[1],
92            littlest[2],
93            littlest[3],
94            littlest[4],
95            littlest[5],
96            littlest[6],
97            littlest[7],
98            biggest[0],
99            biggest[1],
100            biggest[2],
101            biggest[3],
102            biggest[4],
103            biggest[5],
104            biggest[6],
105            biggest[7],
106        ]
107    }
108
109    /// To big endian bytes.
110    pub const fn to_be_bytes(&self) -> [u8; 16] {
111        let biggest = self.seconds.to_be_bytes();
112        let littlest = self.fractions.to_be_bytes();
113        [
114            biggest[0],
115            biggest[1],
116            biggest[2],
117            biggest[3],
118            biggest[4],
119            biggest[5],
120            biggest[6],
121            biggest[7],
122            littlest[0],
123            littlest[1],
124            littlest[2],
125            littlest[3],
126            littlest[4],
127            littlest[5],
128            littlest[6],
129            littlest[7],
130        ]
131    }
132
133    /// From little endian bytes.
134    pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
135        // Ugly but keeps this const compatible.
136        let littlest = [
137            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
138        ];
139        let biggest = [
140            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
141        ];
142        let fraction = u64::from_le_bytes(littlest);
143        let seconds = i64::from_le_bytes(biggest);
144        Self::from_parts(seconds, fraction)
145    }
146
147    /// From big endian bytes.
148    pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
149        let biggest = [
150            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
151        ];
152        let littlest = [
153            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
154        ];
155        let fractions = u64::from_be_bytes(littlest);
156        let seconds = i64::from_be_bytes(biggest);
157        Self::from_parts(seconds, fractions)
158    }
159}
160
161#[cfg(feature = "chrono")]
162mod chrono {
163
164    use super::*;
165    use ::chrono::{DateTime, Utc};
166
167    /// Get the chrono time from the LabVIEW time as a UTC value.
168    ///
169    /// From here you can convert to a specific timezone or naive values.
170    impl TryFrom<&LVTime> for DateTime<Utc> {
171        type Error = LVTimeError;
172
173        fn try_from(value: &LVTime) -> Result<Self, Self::Error> {
174            let seconds_for_time: i64 = value.seconds() - UNIX_EPOCH_IN_LV_SECONDS_I64;
175            let nanoseconds = value.sub_seconds() * 1_000_000_000f64;
176            Self::from_timestamp(seconds_for_time, nanoseconds as u32)
177                .ok_or(LVTimeError::ChronoOutOfRange)
178        }
179    }
180
181    /// Implementation for owned types as well. Probably rarer but kept for backwards
182    /// compatability.
183    impl TryFrom<LVTime> for DateTime<Utc> {
184        type Error = LVTimeError;
185
186        fn try_from(value: LVTime) -> Result<Self, Self::Error> {
187            (&value).try_into()
188        }
189    }
190
191    /// Allow conversion from a chrono time to a LabVIEW time.
192    impl From<DateTime<Utc>> for LVTime {
193        fn from(value: DateTime<Utc>) -> Self {
194            let seconds = value.timestamp();
195            let nanoseconds = value.timestamp_subsec_nanos();
196            let fractional = (nanoseconds as f64) / 1_000_000_000f64;
197            Self::from_unix_epoch(seconds as f64 + fractional)
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    #[test]
206    fn test_to_from_parts() {
207        let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
208        assert_eq!((20, 0x8000_0000_0000_0000), time.to_parts());
209    }
210
211    #[test]
212    fn test_to_from_lv_epoch_seconds() {
213        let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
214        assert_eq!(20.5f64, time.to_lv_epoch());
215        assert_eq!(time, LVTime::from_lv_epoch(20.5f64));
216    }
217
218    #[test]
219    fn test_to_from_unix_epoch() {
220        let time = LVTime::from_parts(3758974472, 0x8000_0000_0000_0000);
221        assert_eq!(1676129672.5f64, time.to_unix_epoch());
222        assert_eq!(time, LVTime::from_unix_epoch(1676129672.5f64));
223    }
224
225    #[test]
226    fn test_to_from_le_bytes() {
227        let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
228        let bytes = time.to_le_bytes();
229        assert_eq!(
230            bytes,
231            [00, 00, 00, 00, 00, 00, 00, 0x80, 0x14, 00, 00, 00, 00, 00, 00, 00]
232        );
233        assert_eq!(time, LVTime::from_le_bytes(bytes));
234    }
235
236    #[test]
237    fn test_to_from_be_bytes() {
238        let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
239        let bytes = time.to_be_bytes();
240        assert_eq!(
241            bytes,
242            [00, 00, 00, 00, 00, 00, 00, 0x14, 0x80, 00, 00, 00, 00, 00, 00, 00]
243        );
244        assert_eq!(time, LVTime::from_be_bytes(bytes));
245    }
246}
247
248#[cfg(test)]
249#[cfg(feature = "chrono")]
250mod chrono_tests {
251
252    use super::{LVTime, UNIX_EPOCH_IN_LV_SECONDS_I64};
253    use chrono::{DateTime, Utc};
254
255    #[test]
256    fn datetime_from_lv_time() {
257        let date_time: DateTime<Utc> = LVTime::from_lv_epoch(3758974472.02440977f64)
258            .try_into()
259            .unwrap();
260        let expected =
261            DateTime::from_timestamp(3758974472 - UNIX_EPOCH_IN_LV_SECONDS_I64, 024409770).unwrap();
262        assert_eq!(date_time, expected);
263    }
264
265    #[test]
266    fn lv_time_from_datetime() {
267        let lv_time = LVTime::from_lv_epoch(3758974472.02440977f64);
268        let date_time: DateTime<Utc> = lv_time.try_into().unwrap();
269        let lv_time_round_trip = date_time.into();
270        assert_eq!(lv_time, lv_time_round_trip);
271    }
272}