use thiserror::Error;
#[derive(Debug, Error)]
pub enum LVTimeError {
#[error("Cannot generate a chrono time as it is out of range.")]
ChronoOutOfRange,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct LVTime {
fractions: u64,
seconds: i64,
}
pub const UNIX_EPOCH_IN_LV_SECONDS_I64: i64 = 2082844800;
pub const UNIX_EPOCH_IN_LV_SECONDS_F64: f64 = UNIX_EPOCH_IN_LV_SECONDS_I64 as f64;
impl LVTime {
pub fn sub_seconds(&self) -> f64 {
let fractional = self.to_parts().1;
(fractional as f64) / 0xFFFF_FFFF_FFFF_FFFFu64 as f64
}
#[inline]
pub const fn seconds(&self) -> i64 {
self.seconds
}
pub fn from_lv_epoch(seconds: f64) -> Self {
let (seconds, fractions) = (seconds / 1.0, seconds % 1.0);
let integer_fractions = (fractions * 0xFFFF_FFFF_FFFF_FFFFu64 as f64) as u64;
Self::from_parts(seconds as i64, integer_fractions)
}
pub fn to_lv_epoch(&self) -> f64 {
self.seconds() as f64 + self.sub_seconds()
}
pub fn to_unix_epoch(&self) -> f64 {
let lv_epoch = self.to_lv_epoch();
lv_epoch - UNIX_EPOCH_IN_LV_SECONDS_F64
}
pub fn from_unix_epoch(seconds: f64) -> Self {
let lv_epoch = seconds + UNIX_EPOCH_IN_LV_SECONDS_F64;
Self::from_lv_epoch(lv_epoch)
}
pub const fn from_parts(seconds: i64, fractions: u64) -> Self {
Self { seconds, fractions }
}
#[inline]
pub const fn to_parts(&self) -> (i64, u64) {
(self.seconds, self.fractions)
}
pub const fn to_le_bytes(&self) -> [u8; 16] {
let littlest = self.fractions.to_le_bytes();
let biggest = self.seconds.to_le_bytes();
[
littlest[0],
littlest[1],
littlest[2],
littlest[3],
littlest[4],
littlest[5],
littlest[6],
littlest[7],
biggest[0],
biggest[1],
biggest[2],
biggest[3],
biggest[4],
biggest[5],
biggest[6],
biggest[7],
]
}
pub const fn to_be_bytes(&self) -> [u8; 16] {
let biggest = self.seconds.to_be_bytes();
let littlest = self.fractions.to_be_bytes();
[
biggest[0],
biggest[1],
biggest[2],
biggest[3],
biggest[4],
biggest[5],
biggest[6],
biggest[7],
littlest[0],
littlest[1],
littlest[2],
littlest[3],
littlest[4],
littlest[5],
littlest[6],
littlest[7],
]
}
pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
let littlest = [
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
];
let biggest = [
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
];
let fraction = u64::from_le_bytes(littlest);
let seconds = i64::from_le_bytes(biggest);
Self::from_parts(seconds, fraction)
}
pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
let biggest = [
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
];
let littlest = [
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
];
let fractions = u64::from_be_bytes(littlest);
let seconds = i64::from_be_bytes(biggest);
Self::from_parts(seconds, fractions)
}
}
#[cfg(feature = "chrono")]
mod chrono {
use super::*;
use ::chrono::{DateTime, Utc};
impl TryFrom<&LVTime> for DateTime<Utc> {
type Error = LVTimeError;
fn try_from(value: &LVTime) -> Result<Self, Self::Error> {
let seconds_for_time: i64 = value.seconds() - UNIX_EPOCH_IN_LV_SECONDS_I64;
let nanoseconds = value.sub_seconds() * 1_000_000_000f64;
Self::from_timestamp(seconds_for_time, nanoseconds as u32)
.ok_or(LVTimeError::ChronoOutOfRange)
}
}
impl TryFrom<LVTime> for DateTime<Utc> {
type Error = LVTimeError;
fn try_from(value: LVTime) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl From<DateTime<Utc>> for LVTime {
fn from(value: DateTime<Utc>) -> Self {
let seconds = value.timestamp();
let nanoseconds = value.timestamp_subsec_nanos();
let fractional = (nanoseconds as f64) / 1_000_000_000f64;
Self::from_unix_epoch(seconds as f64 + fractional)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_from_parts() {
let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
assert_eq!((20, 0x8000_0000_0000_0000), time.to_parts());
}
#[test]
fn test_to_from_lv_epoch_seconds() {
let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
assert_eq!(20.5f64, time.to_lv_epoch());
assert_eq!(time, LVTime::from_lv_epoch(20.5f64));
}
#[test]
fn test_to_from_unix_epoch() {
let time = LVTime::from_parts(3758974472, 0x8000_0000_0000_0000);
assert_eq!(1676129672.5f64, time.to_unix_epoch());
assert_eq!(time, LVTime::from_unix_epoch(1676129672.5f64));
}
#[test]
fn test_to_from_le_bytes() {
let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
let bytes = time.to_le_bytes();
assert_eq!(
bytes,
[00, 00, 00, 00, 00, 00, 00, 0x80, 0x14, 00, 00, 00, 00, 00, 00, 00]
);
assert_eq!(time, LVTime::from_le_bytes(bytes));
}
#[test]
fn test_to_from_be_bytes() {
let time = LVTime::from_parts(20, 0x8000_0000_0000_0000);
let bytes = time.to_be_bytes();
assert_eq!(
bytes,
[00, 00, 00, 00, 00, 00, 00, 0x14, 0x80, 00, 00, 00, 00, 00, 00, 00]
);
assert_eq!(time, LVTime::from_be_bytes(bytes));
}
}
#[cfg(test)]
#[cfg(feature = "chrono")]
mod chrono_tests {
use super::{LVTime, UNIX_EPOCH_IN_LV_SECONDS_I64};
use chrono::{DateTime, Utc};
#[test]
fn datetime_from_lv_time() {
let date_time: DateTime<Utc> = LVTime::from_lv_epoch(3758974472.02440977f64)
.try_into()
.unwrap();
let expected =
DateTime::from_timestamp(3758974472 - UNIX_EPOCH_IN_LV_SECONDS_I64, 024409770).unwrap();
assert_eq!(date_time, expected);
}
#[test]
fn lv_time_from_datetime() {
let lv_time = LVTime::from_lv_epoch(3758974472.02440977f64);
let date_time: DateTime<Utc> = lv_time.try_into().unwrap();
let lv_time_round_trip = date_time.into();
assert_eq!(lv_time, lv_time_round_trip);
}
}