use std::{
cmp::Ordering,
fmt,
io::{Read, Write},
ops::{Add, Sub},
str::FromStr,
};
use chrono::{Duration, SecondsFormat, TimeDelta, TimeZone, Timelike, Utc};
use tracing::error;
use crate::{encoding::*, Context};
const NANOS_PER_SECOND: i64 = 1_000_000_000;
const NANOS_PER_TICK: i64 = 100;
const TICKS_PER_SECOND: i64 = NANOS_PER_SECOND / NANOS_PER_TICK;
const MIN_YEAR: u16 = 1601;
const MAX_YEAR: u16 = 9999;
pub type DateTimeUtc = chrono::DateTime<Utc>;
#[derive(PartialEq, Debug, Clone, Copy, Eq)]
pub struct DateTime {
date_time: DateTimeUtc,
}
impl crate::UaNullable for DateTime {
fn is_ua_null(&self) -> bool {
self.is_null()
}
}
#[cfg(feature = "json")]
mod json {
use crate::{json::*, Error};
use super::DateTime;
impl JsonEncodable for DateTime {
fn encode(
&self,
stream: &mut JsonStreamWriter<&mut dyn std::io::Write>,
_ctx: &crate::Context<'_>,
) -> super::EncodingResult<()> {
Ok(stream.string_value(&self.to_rfc3339())?)
}
}
impl JsonDecodable for DateTime {
fn decode(
stream: &mut JsonStreamReader<&mut dyn std::io::Read>,
_ctx: &Context<'_>,
) -> super::EncodingResult<Self> {
let v = stream.next_str()?;
let dt = DateTime::parse_from_rfc3339(v)
.map_err(|e| Error::decoding(format!("Cannot parse date time \"{v}\": {e}")))?;
Ok(dt)
}
}
}
#[cfg(feature = "xml")]
mod xml {
use crate::xml::*;
use std::io::{Read, Write};
use super::DateTime;
impl XmlType for DateTime {
const TAG: &'static str = "DateTime";
}
impl XmlEncodable for DateTime {
fn encode(
&self,
writer: &mut XmlStreamWriter<&mut dyn Write>,
context: &Context<'_>,
) -> EncodingResult<()> {
self.to_rfc3339().encode(writer, context)
}
}
impl XmlDecodable for DateTime {
fn decode(
read: &mut XmlStreamReader<&mut dyn Read>,
_context: &Context<'_>,
) -> Result<Self, Error> {
let v = read.consume_as_text()?;
let dt = DateTime::parse_from_rfc3339(&v)
.map_err(|e| Error::decoding(format!("Cannot parse date time \"{v}\": {e}")))?;
Ok(dt)
}
}
}
impl BinaryEncodable for DateTime {
fn byte_len(&self, _ctx: &Context<'_>) -> usize {
8
}
fn encode<S: Write + ?Sized>(&self, stream: &mut S, _ctx: &Context<'_>) -> EncodingResult<()> {
let ticks = self.checked_ticks();
write_i64(stream, ticks)
}
}
impl BinaryDecodable for DateTime {
fn decode<S: Read + ?Sized>(stream: &mut S, ctx: &Context<'_>) -> EncodingResult<Self> {
let ticks = read_i64(stream)?;
let date_time = DateTime::from(ticks);
Ok(date_time - ctx.options().client_offset)
}
}
impl Default for DateTime {
fn default() -> Self {
DateTime::epoch()
}
}
impl Add<Duration> for DateTime {
type Output = Self;
fn add(self, duration: Duration) -> Self {
DateTime::from(self.date_time + duration)
}
}
impl Sub<DateTime> for DateTime {
type Output = Duration;
fn sub(self, other: Self) -> Duration {
self.date_time - other.date_time
}
}
impl Sub<Duration> for DateTime {
type Output = Self;
fn sub(self, duration: Duration) -> Self {
DateTime::from(self.date_time - duration)
}
}
impl PartialOrd for DateTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DateTime {
fn cmp(&self, other: &Self) -> Ordering {
self.date_time.cmp(&other.date_time)
}
}
impl From<(u16, u16, u16, u16, u16, u16)> for DateTime {
fn from(dt: (u16, u16, u16, u16, u16, u16)) -> Self {
let (year, month, day, hour, minute, second) = dt;
DateTime::from((year, month, day, hour, minute, second, 0))
}
}
impl From<(u16, u16, u16, u16, u16, u16, u32)> for DateTime {
fn from(dt: (u16, u16, u16, u16, u16, u16, u32)) -> Self {
let (year, month, day, hour, minute, second, nanos) = dt;
if !(1..=12).contains(&month) {
panic!("Invalid month");
}
if !(1..=31).contains(&day) {
panic!("Invalid day");
}
if hour > 23 {
panic!("Invalid hour");
}
if minute > 59 {
panic!("Invalid minute");
}
if second > 59 {
panic!("Invalid second");
}
if nanos as i64 >= NANOS_PER_SECOND {
panic!("Invalid nanosecond");
}
let dt = Utc
.with_ymd_and_hms(
year as i32,
month as u32,
day as u32,
hour as u32,
minute as u32,
second as u32,
)
.unwrap()
.with_nanosecond(nanos)
.unwrap();
DateTime::from(dt)
}
}
impl From<DateTimeUtc> for DateTime {
fn from(date_time: DateTimeUtc) -> Self {
let nanos = (date_time.nanosecond() / NANOS_PER_TICK as u32) * NANOS_PER_TICK as u32;
let date_time = date_time.with_nanosecond(nanos).unwrap();
DateTime { date_time }
}
}
impl From<i64> for DateTime {
fn from(value: i64) -> Self {
if value == i64::MAX {
Self::endtimes()
} else {
let secs = value / TICKS_PER_SECOND;
let nanos = (value - secs * TICKS_PER_SECOND) * NANOS_PER_TICK;
let duration = TimeDelta::try_seconds(secs).unwrap() + Duration::nanoseconds(nanos);
Self::from(Self::epoch_chrono() + duration)
}
}
}
impl From<DateTime> for i64 {
fn from(value: DateTime) -> Self {
value.checked_ticks()
}
}
impl From<DateTime> for DateTimeUtc {
fn from(value: DateTime) -> Self {
value.as_chrono()
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.date_time.to_rfc3339())
}
}
impl FromStr for DateTime {
type Err = chrono::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
DateTimeUtc::from_str(s)
.map(DateTime::from)
.inspect_err(|e| {
error!("Cannot parse date {}, error = {}", s, e);
})
}
}
impl DateTime {
pub fn now() -> DateTime {
DateTime::from(Utc::now())
}
#[cfg(test)]
pub fn rfc3339_now() -> DateTime {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let now = DateTimeUtc::from_timestamp(duration.as_secs() as i64, 0).unwrap();
DateTime::from(now)
}
pub fn now_with_offset(offset: Duration) -> DateTime {
DateTime::from(Utc::now() + offset)
}
pub fn null() -> DateTime {
DateTime::epoch()
}
pub fn is_null(&self) -> bool {
self.ticks() == 0i64
}
pub fn epoch() -> DateTime {
DateTime::from(Self::epoch_chrono())
}
pub fn endtimes() -> DateTime {
DateTime::from(Self::endtimes_chrono())
}
pub fn endtimes_ticks() -> i64 {
Self::duration_to_ticks(Self::endtimes_chrono().signed_duration_since(Self::epoch_chrono()))
}
pub fn ymd(year: u16, month: u16, day: u16) -> DateTime {
DateTime::ymd_hms(year, month, day, 0, 0, 0)
}
pub fn ymd_hms(
year: u16,
month: u16,
day: u16,
hour: u16,
minute: u16,
second: u16,
) -> DateTime {
DateTime::from((year, month, day, hour, minute, second))
}
pub fn ymd_hms_nano(
year: u16,
month: u16,
day: u16,
hour: u16,
minute: u16,
second: u16,
nanos: u32,
) -> DateTime {
DateTime::from((year, month, day, hour, minute, second, nanos))
}
pub fn to_rfc3339(&self) -> String {
self.date_time.to_rfc3339_opts(SecondsFormat::Millis, true)
}
pub fn parse_from_rfc3339(s: &str) -> Result<DateTime, chrono::ParseError> {
let date_time = chrono::DateTime::parse_from_rfc3339(s)?;
let mut date_time = date_time.with_timezone(&Utc);
if date_time < Self::epoch_chrono() {
date_time = Self::epoch_chrono();
}
if date_time > Self::endtimes_chrono() {
date_time = Self::endtimes_chrono();
}
Ok(Self { date_time })
}
pub fn ticks(&self) -> i64 {
Self::duration_to_ticks(self.date_time.signed_duration_since(Self::epoch_chrono()))
}
pub fn checked_ticks(&self) -> i64 {
let nanos = self.ticks();
if nanos < 0 {
return 0;
}
if nanos > Self::endtimes_ticks() {
return i64::MAX;
}
nanos
}
pub fn as_chrono(&self) -> DateTimeUtc {
self.date_time
}
fn epoch_chrono() -> DateTimeUtc {
Utc.with_ymd_and_hms(MIN_YEAR as i32, 1, 1, 0, 0, 0)
.unwrap()
}
fn endtimes_chrono() -> DateTimeUtc {
Utc.with_ymd_and_hms(MAX_YEAR as i32, 12, 31, 23, 59, 59)
.unwrap()
}
fn duration_to_ticks(duration: Duration) -> i64 {
let seconds_part = TimeDelta::try_seconds(duration.num_seconds()).unwrap();
let seconds = seconds_part.num_seconds();
let nanos = (duration - seconds_part).num_nanoseconds().unwrap();
seconds * TICKS_PER_SECOND + nanos / NANOS_PER_TICK
}
}