use crate::Result;
use anyhow::anyhow;
use std::{
hash::{Hash, Hasher},
ops::{Add, AddAssign, Neg, Sub, SubAssign},
time::Duration,
};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum IntervalUnit {
Year,
Month,
Day,
Hour,
Minute,
Second,
Microsecond,
Nanosecond,
}
impl IntervalUnit {
pub fn from_bitmask(mask: u8) -> Result<IntervalUnit> {
Ok(match mask {
1 => IntervalUnit::Nanosecond,
2 => IntervalUnit::Microsecond,
4 => IntervalUnit::Second,
8 => IntervalUnit::Minute,
16 => IntervalUnit::Hour,
32 => IntervalUnit::Day,
64 => IntervalUnit::Month,
128 => IntervalUnit::Year,
_ => return Err(anyhow!("Invalid mask, it must be a single bit on")),
})
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct Interval {
pub months: i32,
pub days: i32,
pub nanos: i64,
}
impl Interval {
pub const ZERO: Interval = Interval {
months: 0,
days: 0,
nanos: 0,
};
pub const DAYS_IN_MONTH: i64 = 30;
pub const DAYS_IN_MONTH_AVG: f64 = 30.436875;
pub const SECS_IN_DAY: i64 = 60 * 60 * 24;
pub const NANOS_IN_SEC: i128 = 1_000_000_000;
pub const NANOS_IN_HOUR: i128 = Self::NANOS_IN_SEC * 3600;
pub const NANOS_IN_DAY: i128 = Self::SECS_IN_DAY as i128 * Self::NANOS_IN_SEC;
pub const MICROS_IN_DAY: i128 = Self::SECS_IN_DAY as i128 * 1_000_000;
pub const fn new(months: i32, days: i32, nanos: i64) -> Self {
Self {
months,
days,
nanos,
}
}
pub const fn from_duration(duration: &Duration) -> Self {
let secs = duration.as_secs();
Self {
months: 0,
days: Self::saturating_i32((secs / Self::SECS_IN_DAY as u64) as i128),
nanos: ((secs % Self::SECS_IN_DAY as u64) as i128 * Self::NANOS_IN_SEC
+ duration.subsec_nanos() as i128) as i64,
}
}
const fn saturating_i32(value: i128) -> i32 {
if value > i32::MAX as i128 {
i32::MAX
} else if value < i32::MIN as i128 {
i32::MIN
} else {
value as i32
}
}
pub const fn from_nanos(value: i128) -> Self {
Self {
months: 0,
days: Self::saturating_i32(value / Self::NANOS_IN_DAY),
nanos: (value % Self::NANOS_IN_DAY) as _,
}
}
pub const fn from_micros(value: i128) -> Self {
const MICROS_IN_DAY: i128 = (Interval::SECS_IN_DAY * 1_000_000) as _;
Self {
months: 0,
days: Self::saturating_i32(value / MICROS_IN_DAY),
nanos: ((value % MICROS_IN_DAY) * 1_000) as _,
}
}
pub const fn from_millis(value: i128) -> Self {
const MILLIS_IN_DAY: i128 = (Interval::SECS_IN_DAY * 1_000) as _;
Self {
months: 0,
days: Self::saturating_i32(value / MILLIS_IN_DAY),
nanos: ((value % MILLIS_IN_DAY) * 1_000_000) as _,
}
}
pub const fn from_secs(value: i64) -> Self {
Self {
months: 0,
days: Self::saturating_i32((value / Self::SECS_IN_DAY) as _),
nanos: ((value % Self::SECS_IN_DAY) * 1_000_000_000) as _,
}
}
pub const fn from_mins(value: i64) -> Self {
const MINS_IN_DAYS: i64 = 60 * 24;
Self {
months: 0,
days: Self::saturating_i32((value / MINS_IN_DAYS) as _),
nanos: (value % MINS_IN_DAYS) * Interval::NANOS_IN_SEC as i64 * 60,
}
}
pub const fn from_hours(value: i64) -> Self {
Self {
months: 0,
days: Self::saturating_i32((value / 24) as _),
nanos: (value % 24) * Interval::NANOS_IN_HOUR as i64,
}
}
pub const fn from_days(value: i64) -> Self {
Self {
months: 0,
days: Self::saturating_i32(value as _),
nanos: 0,
}
}
pub const fn from_weeks(value: i64) -> Self {
Self {
months: 0,
days: Self::saturating_i32(value.saturating_mul(7) as _),
nanos: 0,
}
}
pub const fn from_months(value: i64) -> Self {
Self {
months: Self::saturating_i32(value as _),
days: 0,
nanos: 0,
}
}
pub const fn from_years(value: i64) -> Self {
Self {
months: Self::saturating_i32(value.saturating_mul(12) as _),
days: 0,
nanos: 0,
}
}
pub const fn as_ns(&self) -> i128 {
(self.months as i128)
.saturating_mul(Self::DAYS_IN_MONTH as i128)
.saturating_mul(Self::NANOS_IN_DAY)
.saturating_add(self.days_nanos())
}
pub const fn days_nanos(&self) -> i128 {
(self.days as i128)
.saturating_mul(Self::NANOS_IN_DAY)
.saturating_add(self.nanos as i128)
}
pub const fn as_hmsns(&self) -> (i128, i8, i8, i32) {
let total = self.as_ns();
let abs = total.unsigned_abs();
let ns = (abs % Self::NANOS_IN_SEC as u128) as i32;
let sec = abs / Self::NANOS_IN_SEC as u128;
let s = (sec % 60) as i8;
let min = sec / 60;
let m = (min % 60) as i8;
let h = (min / 60) as i128;
match total < 0 {
true if h > 0 => (-h, m, s, ns),
true if m > 0 => (0, -m, s, ns),
true if s > 0 => (0, 0, -s, ns),
true => (0, 0, 0, -ns),
false => (h, m, s, ns),
}
}
pub const fn is_zero(&self) -> bool {
self.months == 0 && self.days == 0 && self.nanos == 0
}
pub fn as_duration(&self, days_in_month: f64) -> Duration {
let months_nanos = (self.months as f64) * days_in_month * (Interval::NANOS_IN_DAY as f64);
let nanos = (months_nanos as i128).saturating_add(self.days_nanos());
if nanos < 0 {
log::error!("Negative inverval `{self:?}` cannot be converted to `Duration`");
return Duration::ZERO;
}
let secs = (nanos / Interval::NANOS_IN_SEC) as u64;
let nanos = (nanos % Interval::NANOS_IN_SEC) as u32;
Duration::new(secs, nanos)
}
pub const fn units_and_factors(&self) -> &[(IntervalUnit, i128)] {
static UNITS: &[(IntervalUnit, i128)] = &[
(IntervalUnit::Year, Interval::NANOS_IN_DAY * 30 * 12),
(IntervalUnit::Month, Interval::NANOS_IN_DAY * 30),
(IntervalUnit::Day, Interval::NANOS_IN_DAY),
(IntervalUnit::Hour, Interval::NANOS_IN_SEC * 3600),
(IntervalUnit::Minute, Interval::NANOS_IN_SEC * 60),
(IntervalUnit::Second, Interval::NANOS_IN_SEC),
(IntervalUnit::Microsecond, 1_000),
(IntervalUnit::Nanosecond, 1),
];
UNITS
}
pub fn units_mask(&self) -> u8 {
let mut mask = 0_u8;
if self.months != 0 {
if self.months % 12 == 0 {
mask |= 1 << 7;
} else if self.months != 0 {
mask |= 1 << 6;
}
}
let nanos = self.days_nanos();
if nanos != 0 {
for (i, &(_, factor)) in self.units_and_factors().iter().skip(2).enumerate() {
if nanos % factor == 0 {
let shift = 5 - i; mask |= 1 << shift;
break;
}
}
}
mask
}
pub fn unit_value(&self, unit: IntervalUnit) -> i128 {
if unit == IntervalUnit::Year {
self.months as i128 / 12
} else if unit == IntervalUnit::Month {
self.months as i128
} else {
let factor = *self
.units_and_factors()
.iter()
.find_map(|(u, k)| if *u == unit { Some(k) } else { None })
.expect("The unit must be present");
self.days_nanos() / factor
}
}
}
impl PartialEq for Interval {
fn eq(&self, other: &Self) -> bool {
self.months == other.months && self.days_nanos() == other.days_nanos()
}
}
impl Eq for Interval {}
impl Hash for Interval {
fn hash<H: Hasher>(&self, state: &mut H) {
self.months.hash(state);
self.days_nanos().hash(state);
}
}
macro_rules! sum_intervals {
($lhs:ident $op:tt $rhs:ident) => {{
let days_total = $lhs.days as i128 $op $rhs.days as i128
+ ($lhs.nanos as i128) / Interval::NANOS_IN_DAY $op ($rhs.nanos as i128) / Interval::NANOS_IN_DAY;
let days = days_total.clamp(i32::MIN as _, i32::MAX as _);
let mut nanos = ($lhs.nanos as i128) % Interval::NANOS_IN_DAY $op ($rhs.nanos as i128) % Interval::NANOS_IN_DAY;
if days != days_total {
nanos = nanos.saturating_add(
days_total.saturating_sub(days).saturating_mul(Interval::NANOS_IN_DAY),
);
}
Interval {
months: (($lhs.months as i128) $op ($rhs.months as i128))
.clamp(i32::MIN as i128, i32::MAX as i128) as _,
days: days as _,
nanos: nanos.clamp(i64::MIN as i128, i64::MAX as i128) as _,
}
}};
}
impl Add for Interval {
type Output = Interval;
fn add(self, rhs: Self) -> Self {
sum_intervals!(self + rhs)
}
}
impl AddAssign for Interval {
fn add_assign(&mut self, rhs: Self) {
*self = sum_intervals!(self + rhs);
}
}
impl Sub for Interval {
type Output = Interval;
fn sub(self, rhs: Self) -> Self::Output {
sum_intervals!(self - rhs)
}
}
impl SubAssign for Interval {
fn sub_assign(&mut self, rhs: Self) {
*self = sum_intervals!(self - rhs);
}
}
impl Neg for Interval {
type Output = Interval;
fn neg(self) -> Self::Output {
Self::default() - self
}
}
impl From<Duration> for Interval {
fn from(value: Duration) -> Self {
Self::from_duration(&value)
}
}
impl From<Interval> for Duration {
fn from(value: Interval) -> Self {
value.as_duration(Interval::DAYS_IN_MONTH as _)
}
}
impl From<time::Duration> for Interval {
fn from(value: time::Duration) -> Self {
let seconds = value.whole_seconds();
Self {
months: 0,
days: (seconds / Interval::SECS_IN_DAY).clamp(i32::MIN as _, i32::MAX as _) as _,
nanos: (seconds % Interval::SECS_IN_DAY) * Interval::NANOS_IN_SEC as i64
+ value.subsec_nanoseconds() as i64,
}
}
}
impl From<Interval> for time::Duration {
fn from(value: Interval) -> Self {
Self::new(
(value.days as i128)
.saturating_add(
(value.months as i128).saturating_mul(Interval::DAYS_IN_MONTH as i128),
)
.saturating_mul(Interval::SECS_IN_DAY as i128)
.saturating_add(value.nanos as i128 / Interval::NANOS_IN_SEC)
.clamp(i64::MIN as i128, i64::MAX as i128) as i64,
(value.nanos as i128 % Interval::NANOS_IN_SEC) as i32,
)
}
}