#![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)]
use core::{
cmp, fmt,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
time,
};
use crate::{TryFromTimeError, utils::pair_and_then};
const NANOS_PER_SEC: u32 = 1_000_000_000;
const SECS_PER_MINUTE: u64 = 60;
const MINS_PER_HOUR: u64 = 60;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Duration(pub(crate) Option<time::Duration>);
impl Duration {
pub const NONE: Self = Self(None);
pub const ZERO: Self = Self::from_nanos(0);
pub const MAX: Self = Self(Some(time::Duration::MAX));
#[inline]
#[must_use]
pub const fn new(secs: u64, nanos: u32) -> Self {
let secs = time::Duration::from_secs(secs);
let nanos = time::Duration::from_nanos(nanos as u64);
Self(secs.checked_add(nanos))
}
#[inline]
#[must_use]
pub const fn from_secs(secs: u64) -> Self {
Self(Some(time::Duration::from_secs(secs)))
}
#[inline]
#[must_use]
pub const fn from_millis(millis: u64) -> Self {
Self(Some(time::Duration::from_millis(millis)))
}
#[inline]
#[must_use]
pub const fn from_micros(micros: u64) -> Self {
Self(Some(time::Duration::from_micros(micros)))
}
#[inline]
#[must_use]
pub const fn from_nanos(nanos: u64) -> Self {
Self(Some(time::Duration::from_nanos(nanos)))
}
#[inline]
#[must_use]
pub const fn from_nanos_u128(nanos: u128) -> Self {
#[inline]
const fn u64_try_from(v: u128) -> Result<u64, ()> {
if v > (u64::MAX as u128) { Err(()) } else { Ok(v as u64) }
}
const NANOS_PER_SEC: u128 = self::NANOS_PER_SEC as u128;
let secs = match u64_try_from(nanos / NANOS_PER_SEC) {
Ok(secs) => secs,
_ => return Self::NONE,
};
let subsec_nanos = (nanos % NANOS_PER_SEC) as u32;
Self::new(secs, subsec_nanos)
}
#[inline]
#[must_use]
pub const fn from_hours(hours: u64) -> Self {
if hours > u64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR) {
return Self::NONE;
}
Self::from_secs(hours * MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
#[must_use]
pub const fn from_mins(mins: u64) -> Self {
if mins > u64::MAX / SECS_PER_MINUTE {
return Self::NONE;
}
Self::from_secs(mins * SECS_PER_MINUTE)
}
#[inline]
#[must_use]
pub const fn is_zero(&self) -> bool {
matches!((self.as_secs(), self.subsec_nanos()), (Some(0), Some(0)))
}
#[inline]
#[must_use]
pub const fn as_secs(&self) -> Option<u64> {
match &self.0 {
Some(d) => Some(d.as_secs()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn subsec_millis(&self) -> Option<u32> {
match &self.0 {
Some(d) => Some(d.subsec_millis()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn subsec_micros(&self) -> Option<u32> {
match &self.0 {
Some(d) => Some(d.subsec_micros()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn subsec_nanos(&self) -> Option<u32> {
match &self.0 {
Some(d) => Some(d.subsec_nanos()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn as_millis(&self) -> Option<u128> {
match &self.0 {
Some(d) => Some(d.as_millis()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn as_micros(&self) -> Option<u128> {
match &self.0 {
Some(d) => Some(d.as_micros()),
None => None,
}
}
#[inline]
#[must_use]
pub const fn as_nanos(&self) -> Option<u128> {
match &self.0 {
Some(d) => Some(d.as_nanos()),
None => None,
}
}
#[allow(clippy::manual_map)]
#[inline]
#[must_use]
pub fn as_secs_f64(&self) -> Option<f64> {
match &self.0 {
Some(x) => Some(x.as_secs_f64()),
None => None,
}
}
#[allow(clippy::manual_map)]
#[inline]
#[must_use]
pub fn as_secs_f32(&self) -> Option<f32> {
match &self.0 {
Some(x) => Some(x.as_secs_f32()),
None => None,
}
}
#[inline]
#[must_use]
pub fn from_secs_f64(secs: f64) -> Self {
Self(Self::try_from_secs_f64(secs))
}
#[inline]
#[must_use]
pub fn from_secs_f32(secs: f32) -> Self {
Self(Self::try_from_secs_f32(secs))
}
#[inline]
#[must_use]
pub fn mul_f64(self, rhs: f64) -> Self {
self.as_secs_f64().map_or(Self::NONE, |secs| Self::from_secs_f64(rhs * secs))
}
#[inline]
#[must_use]
pub fn mul_f32(self, rhs: f32) -> Self {
self.as_secs_f32().map_or(Self::NONE, |secs| Self::from_secs_f32(rhs * secs))
}
#[inline]
#[must_use]
pub fn div_f64(self, rhs: f64) -> Self {
self.as_secs_f64().map_or(Self::NONE, |secs| Self::from_secs_f64(secs / rhs))
}
#[inline]
#[must_use]
pub fn div_f32(self, rhs: f32) -> Self {
self.as_secs_f32().map_or(Self::NONE, |secs| Self::from_secs_f32(secs / rhs))
}
#[inline]
#[must_use]
pub const fn is_some(&self) -> bool {
self.0.is_some()
}
#[inline]
#[must_use]
pub const fn is_none(&self) -> bool {
!self.is_some()
}
#[inline]
#[must_use]
pub const fn into_inner(self) -> Option<time::Duration> {
self.0
}
#[inline]
#[must_use]
pub const fn unwrap_or(self, default: time::Duration) -> time::Duration {
match self.0 {
Some(d) => d,
None => default,
}
}
#[inline]
pub fn unwrap_or_else<F>(self, default: F) -> time::Duration
where
F: FnOnce() -> time::Duration,
{
self.0.unwrap_or_else(default)
}
}
impl PartialEq<time::Duration> for Duration {
fn eq(&self, other: &time::Duration) -> bool {
self.0 == Some(*other)
}
}
impl PartialEq<Duration> for time::Duration {
fn eq(&self, other: &Duration) -> bool {
other.eq(self)
}
}
impl PartialOrd<time::Duration> for Duration {
fn partial_cmp(&self, other: &time::Duration) -> Option<cmp::Ordering> {
self.0.as_ref().and_then(|this| this.partial_cmp(other))
}
}
impl PartialOrd<Duration> for time::Duration {
fn partial_cmp(&self, other: &Duration) -> Option<cmp::Ordering> {
other.0.as_ref().and_then(|other| self.partial_cmp(other))
}
}
impl Add for Duration {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(pair_and_then(self.0, rhs.0, time::Duration::checked_add))
}
}
impl Add<time::Duration> for Duration {
type Output = Self;
fn add(self, rhs: time::Duration) -> Self::Output {
Self(self.0.and_then(|lhs| lhs.checked_add(rhs)))
}
}
impl AddAssign for Duration {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl AddAssign<time::Duration> for Duration {
fn add_assign(&mut self, rhs: time::Duration) {
*self = *self + rhs;
}
}
impl Sub for Duration {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(pair_and_then(self.0, rhs.0, time::Duration::checked_sub))
}
}
impl Sub<time::Duration> for Duration {
type Output = Self;
fn sub(self, rhs: time::Duration) -> Self::Output {
Self(self.0.and_then(|lhs| lhs.checked_sub(rhs)))
}
}
impl SubAssign for Duration {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl SubAssign<time::Duration> for Duration {
fn sub_assign(&mut self, rhs: time::Duration) {
*self = *self - rhs;
}
}
impl Mul<u32> for Duration {
type Output = Self;
fn mul(self, rhs: u32) -> Self::Output {
Self(self.0.and_then(|lhs| lhs.checked_mul(rhs)))
}
}
impl Mul<Duration> for u32 {
type Output = Duration;
fn mul(self, rhs: Duration) -> Self::Output {
rhs * self
}
}
impl MulAssign<u32> for Duration {
fn mul_assign(&mut self, rhs: u32) {
*self = *self * rhs;
}
}
impl Div<u32> for Duration {
type Output = Self;
fn div(self, rhs: u32) -> Self::Output {
Self(self.0.and_then(|lhs| lhs.checked_div(rhs)))
}
}
impl DivAssign<u32> for Duration {
fn div_assign(&mut self, rhs: u32) {
*self = *self / rhs;
}
}
impl fmt::Debug for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl Default for Duration {
fn default() -> Self {
Self(Some(time::Duration::default()))
}
}
impl From<time::Duration> for Duration {
fn from(dur: time::Duration) -> Self {
Self(Some(dur))
}
}
impl From<Option<time::Duration>> for Duration {
fn from(dur: Option<time::Duration>) -> Self {
Self(dur)
}
}
impl TryFrom<Duration> for time::Duration {
type Error = TryFromTimeError;
fn try_from(dur: Duration) -> Result<Self, Self::Error> {
dur.into_inner().ok_or(TryFromTimeError(()))
}
}
macro_rules! try_from_secs {
(
secs = $secs: expr,
mantissa_bits = $mant_bits: literal,
exponent_bits = $exp_bits: literal,
offset = $offset: literal,
bits_ty = $bits_ty:ty,
double_ty = $double_ty:ty,
) => {{
const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
if $secs < 0.0 {
return None;
}
let bits = $secs.to_bits();
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
let (secs, nanos) = if exp < -31 {
(0u64, 0u32)
} else if exp < 0 {
let t = <$double_ty>::from(mant) << ($offset + exp);
let nanos_offset = $mant_bits + $offset;
let nanos_tmp = u128::from(NANOS_PER_SEC) * u128::from(t);
let nanos = (nanos_tmp >> nanos_offset) as u32;
let rem_mask = (1 << nanos_offset) - 1;
let rem_msb_mask = 1 << (nanos_offset - 1);
let rem = nanos_tmp & rem_mask;
let is_tie = rem == rem_msb_mask;
let is_even = (nanos & 1) == 0;
let rem_msb = nanos_tmp & rem_msb_mask == 0;
let add_ns = !(rem_msb || (is_even && is_tie));
let nanos = nanos + add_ns as u32;
if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) { (0, nanos) } else { (1, 0) }
} else if exp < $mant_bits {
let secs = u64::from(mant >> ($mant_bits - exp));
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
let nanos_offset = $mant_bits;
let nanos_tmp = <$double_ty>::from(NANOS_PER_SEC) * t;
let nanos = (nanos_tmp >> nanos_offset) as u32;
let rem_mask = (1 << nanos_offset) - 1;
let rem_msb_mask = 1 << (nanos_offset - 1);
let rem = nanos_tmp & rem_mask;
let is_tie = rem == rem_msb_mask;
let is_even = (nanos & 1) == 0;
let rem_msb = nanos_tmp & rem_msb_mask == 0;
let add_ns = !(rem_msb || (is_even && is_tie));
let nanos = nanos + add_ns as u32;
if ($mant_bits == 23) || (nanos != NANOS_PER_SEC) {
(secs, nanos)
} else {
(secs + 1, 0)
}
} else if exp < 64 {
let secs = u64::from(mant) << (exp - $mant_bits);
(secs, 0)
} else {
return None;
};
Some(time::Duration::new(secs, nanos))
}};
}
impl Duration {
#[inline]
fn try_from_secs_f32(secs: f32) -> Option<time::Duration> {
try_from_secs!(
secs = secs,
mantissa_bits = 23,
exponent_bits = 8,
offset = 41,
bits_ty = u32,
double_ty = u64,
)
}
#[inline]
fn try_from_secs_f64(secs: f64) -> Option<time::Duration> {
try_from_secs!(
secs = secs,
mantissa_bits = 52,
exponent_bits = 11,
offset = 44,
bits_ty = u64,
double_ty = u128,
)
}
}