use constants::{MAX_TICKS, MIN_TICKS, TICKS_CEILING, TICKS_PER_DAY, TICK_MASK};
use constants::{MIN_DATE_TIME, NANOSECONDS_PER_TICK, TICKS_PER_SECOND};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
use time::{error::IndeterminateOffset, OffsetDateTime, PrimitiveDateTime, UtcOffset};
mod constants;
#[derive(Error, Debug)]
pub enum OutOfRangeError {
#[error("date time is too large")]
TooLarge,
#[error("date time is too small")]
TooSmall,
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("can't determine offset for date time")]
IndeterminateOffset(IndeterminateOffset),
#[error("date time is out of range")]
OutOfRange(OutOfRangeError),
}
fn ticks_to_date_time(ticks: u64) -> PrimitiveDateTime {
MIN_DATE_TIME
+ Duration::new(
ticks / TICKS_PER_SECOND as u64,
(ticks % TICKS_PER_SECOND as u64 * NANOSECONDS_PER_TICK as u64) as u32,
)
}
fn date_time_to_tick(date_time: PrimitiveDateTime) -> i128 {
(date_time - MIN_DATE_TIME).whole_nanoseconds() / 100
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum DateTimeCs {
Local(OffsetDateTime),
Unspecified(PrimitiveDateTime),
Utc(OffsetDateTime),
}
impl DateTimeCs {
pub fn from_binary(date_time_data: i64) -> Result<Self, ParseError> {
let kind = ((date_time_data >> 62) & 3) as u8;
let ticks = date_time_data as u64 & TICK_MASK;
if kind == 0 || kind == 1 {
if ticks > MAX_TICKS {
return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
}
let date_time = ticks_to_date_time(ticks);
return Ok(if kind == 0 {
DateTimeCs::Unspecified(date_time)
} else {
DateTimeCs::Utc(date_time.assume_utc())
});
}
let mut ticks = ticks as i64;
if ticks > TICKS_CEILING as i64 - TICKS_PER_DAY as i64 {
ticks -= TICKS_CEILING as i64;
}
let offset = if ticks > MAX_TICKS as i64 {
UtcOffset::local_offset_at(constants::MAX_DATE_TIME.assume_utc())
} else if ticks < MIN_TICKS as i64 {
UtcOffset::local_offset_at(constants::MIN_DATE_TIME.assume_utc())
} else {
let date_time = ticks_to_date_time(ticks as u64).assume_utc();
UtcOffset::local_offset_at(date_time)
}
.map_err(ParseError::IndeterminateOffset)?;
ticks += offset.whole_seconds() as i64 * 10000000;
if ticks > MAX_TICKS as i64 {
return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
}
Ok(DateTimeCs::Local(
ticks_to_date_time(ticks as u64).assume_offset(offset),
))
}
pub fn to_binary(&self) -> Result<i64, OutOfRangeError> {
let ticks = match self {
DateTimeCs::Utc(d) => date_time_to_tick(d.date().with_time(d.time())),
DateTimeCs::Unspecified(d) => date_time_to_tick(d.to_owned()),
DateTimeCs::Local(d) => {
let ticks = date_time_to_tick(d.date().with_time(d.time()));
if ticks < 0 {
ticks + TICKS_CEILING as i128
} else {
ticks
}
}
};
if ticks >= TICKS_CEILING as i128 {
return Err(OutOfRangeError::TooLarge);
}
if ticks < MIN_TICKS as i128 {
return Err(OutOfRangeError::TooSmall);
}
let kind: i64 = match self {
DateTimeCs::Unspecified(_) => 0,
DateTimeCs::Utc(_) => 1,
DateTimeCs::Local(_) => 2,
};
Ok(ticks as i64 | (kind << 62))
}
}
impl From<PrimitiveDateTime> for DateTimeCs {
fn from(value: PrimitiveDateTime) -> Self {
Self::Unspecified(value)
}
}
impl From<OffsetDateTime> for DateTimeCs {
fn from(value: OffsetDateTime) -> Self {
if value.offset().is_utc() {
Self::Utc(value)
} else {
Self::Local(value)
}
}
}