1use 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#[repr(C)]
18#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
19pub struct LVTime {
20 fractions: u64,
21 seconds: i64,
22}
23
24pub const UNIX_EPOCH_IN_LV_SECONDS_I64: i64 = 2082844800;
28
29pub const UNIX_EPOCH_IN_LV_SECONDS_F64: f64 = UNIX_EPOCH_IN_LV_SECONDS_I64 as f64;
33
34impl LVTime {
35 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 #[inline]
43 pub const fn seconds(&self) -> i64 {
44 self.seconds
45 }
46
47 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 pub fn to_lv_epoch(&self) -> f64 {
58 self.seconds() as f64 + self.sub_seconds()
59 }
60
61 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 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 pub const fn from_parts(seconds: i64, fractions: u64) -> Self {
75 Self { seconds, fractions }
76 }
77
78 #[inline]
80 pub const fn to_parts(&self) -> (i64, u64) {
81 (self.seconds, self.fractions)
82 }
83
84 pub const fn to_le_bytes(&self) -> [u8; 16] {
86 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 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 pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
135 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 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 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 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 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}