use crate::ExpirationDate;
use chrono::{DateTime, TimeDelta, Utc};
use positive::Positive;
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
const SECONDS_PER_DAY: Decimal = Decimal::from_parts(86_400, 0, 0, false, 0);
const NANOS_PER_SECOND: Decimal = Decimal::from_parts(1_000_000_000, 0, 0, false, 0);
impl ExpirationDate {
#[inline]
const fn variant_tag(&self) -> u8 {
match self {
Self::Days(_) => 0,
Self::DateTime(_) => 1,
}
}
}
fn resolve_days(base: DateTime<Utc>, days: Positive) -> Option<DateTime<Utc>> {
let seconds = days.to_dec().checked_mul(SECONDS_PER_DAY)?;
let whole = seconds.trunc();
let nanos = seconds
.checked_sub(whole)?
.checked_mul(NANOS_PER_SECOND)?
.round()
.to_u32()?;
let offset = TimeDelta::try_seconds(whole.to_i64()?)?
.checked_add(&TimeDelta::nanoseconds(i64::from(nanos)))?;
base.checked_add_signed(offset)
}
fn cmp_days_to_instant(days: Positive, instant: &DateTime<Utc>) -> Ordering {
let base = ExpirationDate::get_reference_datetime().unwrap_or_else(Utc::now);
match resolve_days(base, days) {
Some(resolved) => resolved.cmp(instant).then(Ordering::Less),
None => Ordering::Greater,
}
}
impl Hash for ExpirationDate {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u8(self.variant_tag());
match self {
Self::Days(d) => d.hash(state),
Self::DateTime(dt) => {
dt.timestamp().hash(state);
dt.timestamp_subsec_nanos().hash(state);
}
}
}
}
impl PartialEq for ExpirationDate {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Days(s), Self::Days(o)) => s == o,
(Self::DateTime(s), Self::DateTime(o)) => s == o,
_ => false,
}
}
}
impl Eq for ExpirationDate {}
impl PartialOrd for ExpirationDate {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ExpirationDate {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Days(s), Self::Days(o)) => s.cmp(o),
(Self::DateTime(s), Self::DateTime(o)) => s.cmp(o),
(Self::Days(days), Self::DateTime(instant)) => cmp_days_to_instant(*days, instant),
(Self::DateTime(instant), Self::Days(days)) => {
cmp_days_to_instant(*days, instant).reverse()
}
}
}
}