import_stdlib!();
use ops::{Add, Sub};
use chrono::{DateTime, Utc, TimeZone, SecondsFormat, NaiveDate, NaiveDateTime, Timelike};
use anyhow::bail;
use crate::{CBORCodable, CBOREncodable, CBORTaggedEncodable, Tag, CBOR, CBORDecodable, CBORTaggedDecodable, CBORTaggedCodable, CBORTagged};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Date(DateTime<Utc>);
impl Date {
pub fn from_datetime(date_time: DateTime<Utc>) -> Self {
Date(date_time)
}
pub fn from_timestamp(seconds_since_unix_epoch: f64) -> Self {
let whole_seconds_since_unix_epoch = seconds_since_unix_epoch.trunc() as i64;
let nsecs = (seconds_since_unix_epoch.fract() * 1_000_000_000.0) as u32;
Self::from_datetime(Utc.timestamp_opt(whole_seconds_since_unix_epoch, nsecs).unwrap())
}
pub fn new_from_string(value: &str) -> anyhow::Result<Self> {
if let Ok(dt) = DateTime::parse_from_rfc3339(value) {
return Ok(Self::from_datetime(dt.with_timezone(&Utc)));
}
if let Ok(d) = NaiveDate::parse_from_str(value, "%Y-%m-%d") {
let dt = NaiveDateTime::new(d, chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap());
return Ok(Self::from_datetime(DateTime::from_naive_utc_and_offset(dt, Utc)));
}
bail!("Invalid date string")
}
pub fn now() -> Self {
Self::from_datetime(Utc::now())
}
pub fn datetime(&self) -> DateTime<Utc> {
self.0
}
pub fn timestamp(&self) -> f64 {
let d = self.datetime();
let whole_seconds_since_unix_epoch = d.timestamp();
let nsecs = d.nanosecond();
(whole_seconds_since_unix_epoch as f64) + ((nsecs as f64) / 1_000_000_000.0)
}
}
impl Add<f64> for Date {
type Output = Self;
fn add(self, rhs: f64) -> Self::Output {
Self::from_timestamp(self.timestamp() + rhs)
}
}
impl Sub for Date {
type Output = f64;
fn sub(self, rhs: Self) -> Self::Output {
self.timestamp() - rhs.timestamp()
}
}
impl Default for Date {
fn default() -> Self {
Self::now()
}
}
impl TryFrom<&str> for Date {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new_from_string(value)
}
}
impl From<DateTime<Utc>> for Date {
fn from(value: DateTime<Utc>) -> Self {
Self::from_datetime(value)
}
}
impl From<Date> for CBOR {
fn from(value: Date) -> Self {
value.cbor()
}
}
impl AsRef<Date> for Date {
fn as_ref(&self) -> &Self {
self
}
}
impl CBOREncodable for Date {
fn cbor(&self) -> CBOR {
self.tagged_cbor()
}
fn cbor_data(&self) -> Vec<u8> {
self.tagged_cbor().cbor_data()
}
}
impl CBORDecodable for Date {
fn from_cbor(cbor: &CBOR) -> anyhow::Result<Self> {
Self::from_tagged_cbor(cbor)
}
}
impl TryFrom<CBOR> for Date {
type Error = anyhow::Error;
fn try_from(cbor: CBOR) -> Result<Self, Self::Error> {
Self::from_cbor(&cbor)
}
}
impl CBORCodable for Date { }
impl CBORTagged for Date {
fn cbor_tags() -> Vec<Tag> {
vec![Tag::new(1)]
}
}
impl CBORTaggedEncodable for Date {
fn untagged_cbor(&self) -> CBOR {
self.timestamp().cbor()
}
}
impl CBORTaggedDecodable for Date {
fn from_untagged_cbor(cbor: &CBOR) -> anyhow::Result<Self> {
let n = f64::from_cbor(cbor)?;
Ok(Date::from_timestamp(n))
}
}
impl CBORTaggedCodable for Date { }
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.datetime().to_rfc3339_opts(SecondsFormat::Secs, true).as_str())
}
}