import_stdlib!();
#[cfg(not(feature = "std"))]
use core::ops::{Add, Sub};
#[cfg(feature = "std")]
use std::ops::{Add, Sub};
use chrono::{
DateTime, NaiveDate, NaiveDateTime, SecondsFormat, TimeZone, Timelike, Utc,
};
use crate::{
CBOR, CBORTagged, CBORTaggedDecodable, CBORTaggedEncodable, Error, Result,
TAG_DATE, Tag, tags_for_values,
};
#[derive(Debug, Clone, Copy, 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_ymd(year: i32, month: u32, day: u32) -> Self {
let dt = Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap();
Self::from_datetime(dt)
}
pub fn from_ymd_hms(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> Self {
let dt = Utc
.with_ymd_and_hms(year, month, day, hour, minute, second)
.unwrap();
Self::from_datetime(dt)
}
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 from_string(value: impl Into<String>) -> Result<Self> {
let value = value.into();
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),
));
}
Err(Error::InvalidDate("Invalid date string".into()))
}
pub fn now() -> Self { Self::from_datetime(Utc::now()) }
pub fn with_duration_from_now(duration: Duration) -> Self {
Self::now() + duration
}
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<f64> for Date {
type Output = Self;
fn sub(self, rhs: f64) -> Self::Output {
Self::from_timestamp(self.timestamp() - rhs)
}
}
impl Add<Duration> for Date {
type Output = Self;
fn add(self, rhs: Duration) -> Self::Output {
Self::from_timestamp(self.timestamp() + rhs.as_secs_f64())
}
}
impl Sub<Duration> for Date {
type Output = Self;
fn sub(self, rhs: Duration) -> Self::Output {
Self::from_timestamp(self.timestamp() - rhs.as_secs_f64())
}
}
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 = Error;
fn try_from(value: &str) -> Result<Self> { Self::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.tagged_cbor() }
}
impl TryFrom<CBOR> for Date {
type Error = Error;
fn try_from(cbor: CBOR) -> Result<Self> { Self::from_tagged_cbor(cbor) }
}
impl CBORTagged for Date {
fn cbor_tags() -> Vec<Tag> { tags_for_values(&[TAG_DATE]) }
}
impl CBORTaggedEncodable for Date {
fn untagged_cbor(&self) -> CBOR { self.timestamp().into() }
}
impl CBORTaggedDecodable for Date {
fn from_untagged_cbor(cbor: CBOR) -> Result<Self> {
let n = cbor.clone().try_into()?;
Ok(Date::from_timestamp(n))
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dt = self.datetime();
if dt.hour() == 0 && dt.minute() == 0 && dt.second() == 0 {
f.write_str(dt.date_naive().to_string().as_str())
} else {
f.write_str(dt.to_rfc3339_opts(SecondsFormat::Secs, true).as_str())
}
}
}