use core::{fmt, time::Duration as StdDuration};
#[macro_export]
#[doc(hidden)]
macro_rules! const_try_opt {
($e:expr) => {
match $e {
Some(value) => value,
None => return None,
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! expect_opt {
($e:expr, $message:literal) => {
match $e {
Some(value) => value,
None => expect_failed($message),
}
};
}
#[inline(never)]
#[cold]
#[track_caller]
const fn expect_failed(message: &str) -> ! {
panic!("{}", message)
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) enum Padding {
#[allow(clippy::missing_docs_in_private_items)]
#[default]
Optimize,
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Duration {
seconds: i64,
nanoseconds: i32, #[allow(clippy::missing_docs_in_private_items)]
padding: Padding,
}
impl fmt::Debug for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Duration")
.field("seconds", &self.seconds)
.field("nanoseconds", &self.nanoseconds)
.finish()
}
}
impl Duration {
pub const ZERO: Self = Self::seconds(0);
pub const NANOSECOND: Self = Self::nanoseconds(1);
pub const MICROSECOND: Self = Self::microseconds(1);
pub const MILLISECOND: Self = Self::milliseconds(1);
pub const SECOND: Self = Self::seconds(1);
pub const MINUTE: Self = Self::minutes(1);
pub const HOUR: Self = Self::hours(1);
pub const DAY: Self = Self::days(1);
pub const WEEK: Self = Self::weeks(1);
pub const MIN: Self = Self::new_unchecked(i64::MIN, -999_999_999);
pub const MAX: Self = Self::new_unchecked(i64::MAX, 999_999_999);
pub const fn is_zero(self) -> bool {
self.seconds == 0 && self.nanoseconds == 0
}
pub const fn is_negative(self) -> bool {
self.seconds < 0 || self.nanoseconds < 0
}
pub const fn is_positive(self) -> bool {
self.seconds > 0 || self.nanoseconds > 0
}
pub const fn abs(self) -> Self {
Self::new_unchecked(self.seconds.saturating_abs(), self.nanoseconds.abs())
}
pub const fn unsigned_abs(self) -> StdDuration {
StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs())
}
pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
if seconds < 0 {
debug_assert!(nanoseconds <= 0);
debug_assert!(nanoseconds > -1_000_000_000);
} else if seconds > 0 {
debug_assert!(nanoseconds >= 0);
debug_assert!(nanoseconds < 1_000_000_000);
} else {
debug_assert!(nanoseconds.unsigned_abs() < 1_000_000_000);
}
Self {
seconds,
nanoseconds,
padding: Padding::Optimize,
}
}
pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
seconds = expect_opt!(
seconds.checked_add(nanoseconds as i64 / 1_000_000_000),
"overflow constructing `time::Duration`"
);
nanoseconds %= 1_000_000_000;
if seconds > 0 && nanoseconds < 0 {
seconds -= 1;
nanoseconds += 1_000_000_000;
} else if seconds < 0 && nanoseconds > 0 {
seconds += 1;
nanoseconds -= 1_000_000_000;
}
Self::new_unchecked(seconds, nanoseconds)
}
pub const fn weeks(weeks: i64) -> Self {
Self::seconds(expect_opt!(
weeks.checked_mul(604_800),
"overflow constructing `time::Duration`"
))
}
#[doc(hidden)]
pub const fn years(years: i64) -> Self {
Self::seconds(expect_opt!(
years.checked_mul(604_800 * 4 * 12),
"overflow constructing `time::Duration`"
))
}
pub const fn days(days: i64) -> Self {
Self::seconds(expect_opt!(
days.checked_mul(86_400),
"overflow constructing `time::Duration`"
))
}
pub const fn hours(hours: i64) -> Self {
Self::seconds(expect_opt!(
hours.checked_mul(3_600),
"overflow constructing `time::Duration`"
))
}
pub const fn minutes(minutes: i64) -> Self {
Self::seconds(expect_opt!(
minutes.checked_mul(60),
"overflow constructing `time::Duration`"
))
}
pub const fn seconds(seconds: i64) -> Self {
Self::new_unchecked(seconds, 0)
}
pub const fn milliseconds(milliseconds: i64) -> Self {
Self::new_unchecked(
milliseconds / 1_000,
((milliseconds % 1_000) * 1_000_000) as _,
)
}
pub const fn microseconds(microseconds: i64) -> Self {
Self::new_unchecked(
microseconds / 1_000_000,
((microseconds % 1_000_000) * 1_000) as _,
)
}
pub const fn nanoseconds(nanoseconds: i64) -> Self {
Self::new_unchecked(
nanoseconds / 1_000_000_000,
(nanoseconds % 1_000_000_000) as _,
)
}
pub const fn whole_weeks(self) -> i64 {
self.whole_seconds() / 604_800
}
pub const fn whole_days(self) -> i64 {
self.whole_seconds() / 86_400
}
pub const fn whole_hours(self) -> i64 {
self.whole_seconds() / 3_600
}
pub const fn whole_minutes(self) -> i64 {
self.whole_seconds() / 60
}
pub const fn whole_seconds(self) -> i64 {
self.seconds
}
pub fn as_seconds_f64(self) -> f64 {
self.seconds as f64 + self.nanoseconds as f64 / 1_000_000_000.
}
pub fn as_seconds_f32(self) -> f32 {
self.seconds as f32 + self.nanoseconds as f32 / 1_000_000_000.
}
pub const fn whole_milliseconds(self) -> i128 {
self.seconds as i128 * 1_000 + self.nanoseconds as i128 / 1_000_000
}
pub const fn subsec_milliseconds(self) -> i16 {
(self.nanoseconds / 1_000_000) as _
}
pub const fn whole_microseconds(self) -> i128 {
self.seconds as i128 * 1_000_000 + self.nanoseconds as i128 / 1_000
}
pub const fn subsec_microseconds(self) -> i32 {
self.nanoseconds / 1_000
}
pub const fn whole_nanoseconds(self) -> i128 {
self.seconds as i128 * 1_000_000_000 + self.nanoseconds as i128
}
pub const fn subsec_nanoseconds(self) -> i32 {
self.nanoseconds
}
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
let mut seconds = const_try_opt!(self.seconds.checked_add(rhs.seconds));
let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
nanoseconds -= 1_000_000_000;
seconds = const_try_opt!(seconds.checked_add(1));
} else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
nanoseconds += 1_000_000_000;
seconds = const_try_opt!(seconds.checked_sub(1));
}
Some(Self::new_unchecked(seconds, nanoseconds))
}
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
let mut seconds = const_try_opt!(self.seconds.checked_sub(rhs.seconds));
let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
nanoseconds -= 1_000_000_000;
seconds = const_try_opt!(seconds.checked_add(1));
} else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
nanoseconds += 1_000_000_000;
seconds = const_try_opt!(seconds.checked_sub(1));
}
Some(Self::new_unchecked(seconds, nanoseconds))
}
pub const fn checked_mul(self, rhs: i32) -> Option<Self> {
let total_nanos = self.nanoseconds as i64 * rhs as i64;
let extra_secs = total_nanos / 1_000_000_000;
let nanoseconds = (total_nanos % 1_000_000_000) as _;
let seconds = const_try_opt!(
const_try_opt!(self.seconds.checked_mul(rhs as _)).checked_add(extra_secs)
);
Some(Self::new_unchecked(seconds, nanoseconds))
}
pub const fn checked_div(self, rhs: i32) -> Option<Self> {
let seconds = const_try_opt!(self.seconds.checked_div(rhs as i64));
let carry = self.seconds - seconds * (rhs as i64);
let extra_nanos = const_try_opt!((carry * 1_000_000_000).checked_div(rhs as i64));
let nanoseconds = const_try_opt!(self.nanoseconds.checked_div(rhs)) + (extra_nanos as i32);
Some(Self::new_unchecked(seconds, nanoseconds))
}
pub const fn saturating_add(self, rhs: Self) -> Self {
let (mut seconds, overflow) = self.seconds.overflowing_add(rhs.seconds);
if overflow {
if self.seconds > 0 {
return Self::MAX;
}
return Self::MIN;
}
let mut nanoseconds = self.nanoseconds + rhs.nanoseconds;
if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
nanoseconds -= 1_000_000_000;
seconds = match seconds.checked_add(1) {
Some(seconds) => seconds,
None => return Self::MAX,
};
} else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
nanoseconds += 1_000_000_000;
seconds = match seconds.checked_sub(1) {
Some(seconds) => seconds,
None => return Self::MIN,
};
}
Self::new_unchecked(seconds, nanoseconds)
}
pub const fn saturating_sub(self, rhs: Self) -> Self {
let (mut seconds, overflow) = self.seconds.overflowing_sub(rhs.seconds);
if overflow {
if self.seconds > 0 {
return Self::MAX;
}
return Self::MIN;
}
let mut nanoseconds = self.nanoseconds - rhs.nanoseconds;
if nanoseconds >= 1_000_000_000 || seconds < 0 && nanoseconds > 0 {
nanoseconds -= 1_000_000_000;
seconds = match seconds.checked_add(1) {
Some(seconds) => seconds,
None => return Self::MAX,
};
} else if nanoseconds <= -1_000_000_000 || seconds > 0 && nanoseconds < 0 {
nanoseconds += 1_000_000_000;
seconds = match seconds.checked_sub(1) {
Some(seconds) => seconds,
None => return Self::MIN,
};
}
Self::new_unchecked(seconds, nanoseconds)
}
pub const fn saturating_mul(self, rhs: i32) -> Self {
let total_nanos = self.nanoseconds as i64 * rhs as i64;
let extra_secs = total_nanos / 1_000_000_000;
let nanoseconds = (total_nanos % 1_000_000_000) as _;
let (seconds, overflow1) = self.seconds.overflowing_mul(rhs as _);
if overflow1 {
if self.seconds > 0 && rhs > 0 || self.seconds < 0 && rhs < 0 {
return Self::MAX;
}
return Self::MIN;
}
let (seconds, overflow2) = seconds.overflowing_add(extra_secs);
if overflow2 {
if self.seconds > 0 && rhs > 0 {
return Self::MAX;
}
return Self::MIN;
}
Self::new_unchecked(seconds, nanoseconds)
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_negative() {
f.write_str("-")?;
}
if let Some(_precision) = f.precision() {
if self.is_zero() {
return (0.).fmt(f).and_then(|_| f.write_str("s"));
}
macro_rules! item {
($name:literal, $value:expr) => {
let value = $value;
if value >= 1.0 {
return value.fmt(f).and_then(|_| f.write_str($name));
}
};
}
let seconds = self.unsigned_abs().as_secs_f64();
item!("d", seconds / 86_400.);
item!("h", seconds / 3_600.);
item!("m", seconds / 60.);
item!("s", seconds);
item!("ms", seconds * 1_000.);
item!("µs", seconds * 1_000_000.);
item!("ns", seconds * 1_000_000_000.);
} else {
if self.is_zero() {
return f.write_str("0s");
}
macro_rules! item {
($name:literal, $value:expr) => {
match $value {
0 => Ok(()),
value => value.fmt(f).and_then(|_| f.write_str($name)),
}
};
}
let seconds = self.seconds.unsigned_abs();
let nanoseconds = self.nanoseconds.unsigned_abs();
item!("d", seconds / 86_400)?;
item!("h", seconds / 3_600 % 24)?;
item!("m", seconds / 60 % 60)?;
item!("s", seconds % 60)?;
item!("ms", nanoseconds / 1_000_000)?;
item!("µs", nanoseconds / 1_000 % 1_000)?;
item!("ns", nanoseconds % 1_000)?;
}
Ok(())
}
}
mod sealed {
pub trait Sealed {}
impl Sealed for i64 {}
impl Sealed for u64 {}
impl Sealed for f64 {}
}
#[doc(hidden)]
pub trait NumericalDuration: sealed::Sealed {
fn nanoseconds(self) -> Duration;
fn microseconds(self) -> Duration;
fn milliseconds(self) -> Duration;
fn seconds(self) -> Duration;
fn minutes(self) -> Duration;
fn hours(self) -> Duration;
fn days(self) -> Duration;
fn weeks(self) -> Duration;
fn years(self) -> Duration;
}
impl NumericalDuration for i64 {
fn nanoseconds(self) -> Duration {
Duration::nanoseconds(self)
}
fn microseconds(self) -> Duration {
Duration::microseconds(self)
}
fn milliseconds(self) -> Duration {
Duration::milliseconds(self)
}
fn seconds(self) -> Duration {
Duration::seconds(self)
}
fn minutes(self) -> Duration {
Duration::minutes(self)
}
fn hours(self) -> Duration {
Duration::hours(self)
}
fn days(self) -> Duration {
Duration::days(self)
}
fn weeks(self) -> Duration {
Duration::weeks(self)
}
fn years(self) -> Duration {
Duration::years(self)
}
}