use core::time::Duration;
use crate::{
civil::{Date, DateTime, Time},
error::{signed_duration::Error as E, ErrorContext},
fmt::{friendly, temporal},
tz::Offset,
util::{
b::{self, SpecialBoundsError},
round::Increment,
},
Error, RoundMode, Timestamp, Unit, Zoned,
};
const NANOS_PER_SEC: i32 = 1_000_000_000;
const NANOS_PER_MILLI: i32 = 1_000_000;
const NANOS_PER_MICRO: i32 = 1_000;
const MILLIS_PER_SEC: i64 = 1_000;
const MICROS_PER_SEC: i64 = 1_000_000;
const SECS_PER_MINUTE: i64 = 60;
const MINS_PER_HOUR: i64 = 60;
const HOURS_PER_CIVIL_DAY: i64 = 24;
const DAYS_PER_WEEK: i64 = 7;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SignedDuration {
secs: i64,
nanos: i32,
}
impl SignedDuration {
pub const ZERO: SignedDuration = SignedDuration { secs: 0, nanos: 0 };
pub const MIN: SignedDuration =
SignedDuration { secs: i64::MIN, nanos: -(NANOS_PER_SEC - 1) };
pub const MAX: SignedDuration =
SignedDuration { secs: i64::MAX, nanos: NANOS_PER_SEC - 1 };
#[inline]
pub const fn new(mut secs: i64, mut nanos: i32) -> SignedDuration {
if !(-NANOS_PER_SEC < nanos && nanos < NANOS_PER_SEC) {
let addsecs = nanos / NANOS_PER_SEC;
secs = match secs.checked_add(addsecs as i64) {
Some(secs) => secs,
None => panic!(
"nanoseconds overflowed seconds in SignedDuration::new"
),
};
nanos = nanos % NANOS_PER_SEC;
}
if nanos == 0 || secs == 0 || secs.signum() == (nanos.signum() as i64)
{
return SignedDuration::new_unchecked(secs, nanos);
}
if secs < 0 {
debug_assert!(nanos > 0);
secs += 1;
nanos -= NANOS_PER_SEC;
} else {
debug_assert!(secs > 0);
debug_assert!(nanos < 0);
secs -= 1;
nanos += NANOS_PER_SEC;
}
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub(crate) const fn new_without_nano_overflow(
secs: i64,
nanos: i32,
) -> SignedDuration {
assert!(nanos <= 999_999_999);
assert!(nanos >= -999_999_999);
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
const fn new_unchecked(secs: i64, nanos: i32) -> SignedDuration {
debug_assert!(nanos <= 999_999_999);
debug_assert!(nanos >= -999_999_999);
SignedDuration { secs, nanos }
}
#[inline]
pub const fn from_secs(secs: i64) -> SignedDuration {
SignedDuration::new_unchecked(secs, 0)
}
#[inline]
pub const fn from_millis(millis: i64) -> SignedDuration {
let secs = millis / MILLIS_PER_SEC;
let nanos = (millis % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_millis_i128(millis: i128) -> SignedDuration {
match SignedDuration::try_from_millis_i128(millis) {
Some(sdur) => sdur,
None => {
panic!(
"seconds overflows `i64` \
in `SignedDuration::from_millis_i128`",
)
}
}
}
#[inline]
pub const fn from_micros(micros: i64) -> SignedDuration {
let secs = micros / MICROS_PER_SEC;
let nanos = (micros % MICROS_PER_SEC) as i32 * NANOS_PER_MICRO;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_micros_i128(micros: i128) -> SignedDuration {
match SignedDuration::try_from_micros_i128(micros) {
Some(sdur) => sdur,
None => {
panic!(
"seconds overflows `i64` \
in `SignedDuration::from_micros_i128`",
)
}
}
}
#[inline]
pub const fn from_nanos(nanos: i64) -> SignedDuration {
const NANOS_PER_SEC: i64 = self::NANOS_PER_SEC as i64;
let secs = nanos / NANOS_PER_SEC;
let nanos = (nanos % NANOS_PER_SEC) as i32;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_nanos_i128(nanos: i128) -> SignedDuration {
match SignedDuration::try_from_nanos_i128(nanos) {
Some(sdur) => sdur,
None => {
panic!(
"seconds overflows `i64` \
in `SignedDuration::from_nanos_i128`",
)
}
}
}
#[inline]
pub const fn from_hours(hours: i64) -> SignedDuration {
match SignedDuration::try_from_hours(hours) {
Some(sdur) => sdur,
None => {
panic!(
"hours overflowed an `i64` number of seconds \
in `SignedDuration::from_hours`",
)
}
}
}
#[inline]
pub const fn from_mins(mins: i64) -> SignedDuration {
match SignedDuration::try_from_mins(mins) {
Some(sdur) => sdur,
None => {
panic!(
"minutes overflowed an `i64` number of seconds \
in `SignedDuration::from_mins`",
)
}
}
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.secs == 0 && self.nanos == 0
}
#[inline]
pub const fn as_secs(&self) -> i64 {
self.secs
}
#[inline]
pub const fn subsec_millis(&self) -> i32 {
self.nanos / NANOS_PER_MILLI
}
#[inline]
pub const fn subsec_micros(&self) -> i32 {
self.nanos / NANOS_PER_MICRO
}
#[inline]
pub const fn subsec_nanos(&self) -> i32 {
self.nanos
}
#[inline]
pub const fn as_millis(&self) -> i128 {
let millis = (self.secs as i128) * (MILLIS_PER_SEC as i128);
let subsec_millis = (self.nanos / NANOS_PER_MILLI) as i128;
millis + subsec_millis
}
#[inline]
pub const fn as_micros(&self) -> i128 {
let micros = (self.secs as i128) * (MICROS_PER_SEC as i128);
let subsec_micros = (self.nanos / NANOS_PER_MICRO) as i128;
micros + subsec_micros
}
#[inline]
pub const fn as_nanos(&self) -> i128 {
let nanos = (self.secs as i128) * (NANOS_PER_SEC as i128);
nanos + (self.nanos as i128)
}
#[inline]
pub(crate) fn as_nanos64(&self) -> Option<i64> {
const MIN: SignedDuration = SignedDuration::from_nanos(i64::MIN);
const MAX: SignedDuration = SignedDuration::from_nanos(i64::MAX);
if MIN <= *self && *self <= MAX {
let nanos = self.secs * (NANOS_PER_SEC as i64);
Some(nanos + (self.nanos as i64))
} else {
None
}
}
#[inline]
pub const fn checked_add(
self,
rhs: SignedDuration,
) -> Option<SignedDuration> {
let Some(mut secs) = self.secs.checked_add(rhs.secs) else {
return None;
};
let mut nanos = self.nanos + rhs.nanos;
if nanos != 0 {
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs = match secs.checked_add(1) {
None => return None,
Some(secs) => secs,
};
} else if nanos <= -NANOS_PER_SEC {
nanos += NANOS_PER_SEC;
secs = match secs.checked_sub(1) {
None => return None,
Some(secs) => secs,
};
}
if secs != 0
&& nanos != 0
&& secs.signum() != (nanos.signum() as i64)
{
if secs < 0 {
debug_assert!(nanos > 0);
secs += 1;
nanos -= NANOS_PER_SEC;
} else {
debug_assert!(secs > 0);
debug_assert!(nanos < 0);
secs -= 1;
nanos += NANOS_PER_SEC;
}
}
}
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub const fn saturating_add(self, rhs: SignedDuration) -> SignedDuration {
let Some(sum) = self.checked_add(rhs) else {
return if rhs.is_negative() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
sum
}
#[inline]
pub const fn checked_sub(
self,
rhs: SignedDuration,
) -> Option<SignedDuration> {
let Some(rhs) = rhs.checked_neg() else { return None };
self.checked_add(rhs)
}
#[inline]
pub const fn saturating_sub(self, rhs: SignedDuration) -> SignedDuration {
let Some(diff) = self.checked_sub(rhs) else {
return if rhs.is_positive() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
diff
}
#[inline]
pub const fn checked_mul(self, rhs: i32) -> Option<SignedDuration> {
let rhs = rhs as i64;
let nanos = (self.nanos as i64) * rhs;
let addsecs = nanos / (NANOS_PER_SEC as i64);
let nanos = (nanos % (NANOS_PER_SEC as i64)) as i32;
let Some(secs) = self.secs.checked_mul(rhs) else { return None };
let Some(secs) = secs.checked_add(addsecs) else { return None };
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub const fn saturating_mul(self, rhs: i32) -> SignedDuration {
let Some(product) = self.checked_mul(rhs) else {
let sign = (self.signum() as i64) * (rhs as i64).signum();
return if sign.is_negative() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
product
}
#[inline]
pub const fn checked_div(self, rhs: i32) -> Option<SignedDuration> {
if rhs == 0 || (self.secs == i64::MIN && rhs == -1) {
return None;
}
let secs = self.secs / (rhs as i64);
let addsecs = self.secs % (rhs as i64);
let mut nanos = self.nanos / rhs;
let addnanos = self.nanos % rhs;
let leftover_nanos =
(addsecs * (NANOS_PER_SEC as i64)) + (addnanos as i64);
nanos += (leftover_nanos / (rhs as i64)) as i32;
debug_assert!(nanos < NANOS_PER_SEC);
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub fn as_secs_f64(&self) -> f64 {
(self.secs as f64) + ((self.nanos as f64) / (NANOS_PER_SEC as f64))
}
#[inline]
pub fn as_secs_f32(&self) -> f32 {
(self.secs as f32) + ((self.nanos as f32) / (NANOS_PER_SEC as f32))
}
#[inline]
pub fn as_millis_f64(&self) -> f64 {
((self.secs as f64) * (MILLIS_PER_SEC as f64))
+ ((self.nanos as f64) / (NANOS_PER_MILLI as f64))
}
#[inline]
pub fn as_millis_f32(&self) -> f32 {
((self.secs as f32) * (MILLIS_PER_SEC as f32))
+ ((self.nanos as f32) / (NANOS_PER_MILLI as f32))
}
#[inline]
pub fn from_secs_f64(secs: f64) -> SignedDuration {
SignedDuration::try_from_secs_f64(secs)
.expect("finite and in-bounds f64")
}
#[inline]
pub fn from_secs_f32(secs: f32) -> SignedDuration {
SignedDuration::try_from_secs_f32(secs)
.expect("finite and in-bounds f32")
}
#[inline]
pub fn try_from_secs_f64(secs: f64) -> Result<SignedDuration, Error> {
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
if !secs.is_finite() {
return Err(Error::from(E::ConvertNonFinite));
}
if !((i64::MIN as f64) <= secs && secs <= (i64::MAX as f64)) {
return Err(
SpecialBoundsError::SignedDurationFloatOutOfRangeF64.into()
);
}
let mut int_secs = secs.trunc() as i64;
let mut int_nanos =
(secs.fract() * (NANOS_PER_SEC as f64)).round() as i32;
if int_nanos.unsigned_abs() == 1_000_000_000 {
let increment = i64::from(int_nanos.signum());
int_secs = int_secs
.checked_add(increment)
.ok_or_else(b::SignedDurationSeconds::error)?;
int_nanos = 0;
}
Ok(SignedDuration::new_unchecked(int_secs, int_nanos))
}
#[inline]
pub fn try_from_secs_f32(secs: f32) -> Result<SignedDuration, Error> {
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
if !secs.is_finite() {
return Err(Error::from(E::ConvertNonFinite));
}
if !((i64::MIN as f32) <= secs && secs <= (i64::MAX as f32)) {
return Err(
SpecialBoundsError::SignedDurationFloatOutOfRangeF32.into()
);
}
let mut int_nanos =
(secs.fract() * (NANOS_PER_SEC as f32)).round() as i32;
let mut int_secs = secs.trunc() as i64;
if int_nanos.unsigned_abs() == 1_000_000_000 {
let increment = i64::from(int_nanos.signum());
int_secs = int_secs
.checked_add(increment)
.ok_or_else(b::SignedDurationSeconds::error)?;
int_nanos = 0;
}
Ok(SignedDuration::new_unchecked(int_secs, int_nanos))
}
#[inline]
pub fn mul_f64(self, rhs: f64) -> SignedDuration {
SignedDuration::from_secs_f64(rhs * self.as_secs_f64())
}
#[inline]
pub fn mul_f32(self, rhs: f32) -> SignedDuration {
SignedDuration::from_secs_f32(rhs * self.as_secs_f32())
}
#[inline]
pub fn div_f64(self, rhs: f64) -> SignedDuration {
SignedDuration::from_secs_f64(self.as_secs_f64() / rhs)
}
#[inline]
pub fn div_f32(self, rhs: f32) -> SignedDuration {
SignedDuration::from_secs_f32(self.as_secs_f32() / rhs)
}
#[inline]
pub fn div_duration_f64(self, rhs: SignedDuration) -> f64 {
let lhs_nanos =
(self.secs as f64) * (NANOS_PER_SEC as f64) + (self.nanos as f64);
let rhs_nanos =
(rhs.secs as f64) * (NANOS_PER_SEC as f64) + (rhs.nanos as f64);
lhs_nanos / rhs_nanos
}
#[inline]
pub fn div_duration_f32(self, rhs: SignedDuration) -> f32 {
let lhs_nanos =
(self.secs as f32) * (NANOS_PER_SEC as f32) + (self.nanos as f32);
let rhs_nanos =
(rhs.secs as f32) * (NANOS_PER_SEC as f32) + (rhs.nanos as f32);
lhs_nanos / rhs_nanos
}
}
impl SignedDuration {
#[inline]
pub const fn try_from_hours(hours: i64) -> Option<SignedDuration> {
const MIN_HOUR: i64 = i64::MIN / (SECS_PER_MINUTE * MINS_PER_HOUR);
const MAX_HOUR: i64 = i64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR);
if !(MIN_HOUR <= hours && hours <= MAX_HOUR) {
return None;
}
Some(SignedDuration::from_secs(
hours * MINS_PER_HOUR * SECS_PER_MINUTE,
))
}
#[inline]
pub const fn try_from_mins(mins: i64) -> Option<SignedDuration> {
const MIN_MINUTE: i64 = i64::MIN / SECS_PER_MINUTE;
const MAX_MINUTE: i64 = i64::MAX / SECS_PER_MINUTE;
if !(MIN_MINUTE <= mins && mins <= MAX_MINUTE) {
return None;
}
Some(SignedDuration::from_secs(mins * SECS_PER_MINUTE))
}
#[inline]
pub const fn try_from_millis_i128(millis: i128) -> Option<SignedDuration> {
const MILLIS_PER_SEC: i128 = self::MILLIS_PER_SEC as i128;
let secs = millis / MILLIS_PER_SEC;
if !(i64::MIN as i128 <= secs && secs <= i64::MAX as i128) {
return None;
}
let secs64 = secs as i64;
let nanos = (millis % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI;
Some(SignedDuration::new_unchecked(secs64, nanos))
}
#[inline]
pub const fn try_from_micros_i128(micros: i128) -> Option<SignedDuration> {
const MICROS_PER_SEC: i128 = self::MICROS_PER_SEC as i128;
let secs = micros / MICROS_PER_SEC;
if !(i64::MIN as i128 <= secs && secs <= i64::MAX as i128) {
return None;
}
let secs64 = secs as i64;
let nanos = (micros % MICROS_PER_SEC) as i32 * NANOS_PER_MICRO;
Some(SignedDuration::new_unchecked(secs64, nanos))
}
#[inline]
pub const fn try_from_nanos_i128(nanos: i128) -> Option<SignedDuration> {
const NANOS_PER_SEC: i128 = self::NANOS_PER_SEC as i128;
let secs = nanos / NANOS_PER_SEC;
if !(i64::MIN as i128 <= secs && secs <= i64::MAX as i128) {
return None;
}
let secs64 = secs as i64;
let nanos = (nanos % NANOS_PER_SEC) as i32;
Some(SignedDuration::new_unchecked(secs64, nanos))
}
#[inline]
pub const fn as_hours(&self) -> i64 {
self.as_secs() / (MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
pub const fn as_mins(&self) -> i64 {
self.as_secs() / SECS_PER_MINUTE
}
#[inline]
pub const fn abs(self) -> SignedDuration {
SignedDuration::new_unchecked(self.secs.abs(), self.nanos.abs())
}
#[inline]
pub const fn unsigned_abs(self) -> Duration {
Duration::new(self.secs.unsigned_abs(), self.nanos.unsigned_abs())
}
#[inline]
pub const fn checked_neg(self) -> Option<SignedDuration> {
let Some(secs) = self.secs.checked_neg() else { return None };
Some(SignedDuration::new_unchecked(
secs,
-self.nanos,
))
}
#[inline]
pub const fn signum(self) -> i8 {
if self.is_zero() {
0
} else if self.is_positive() {
1
} else {
debug_assert!(self.is_negative());
-1
}
}
#[inline]
pub const fn is_positive(&self) -> bool {
self.secs.is_positive() || self.nanos.is_positive()
}
#[inline]
pub const fn is_negative(&self) -> bool {
self.secs.is_negative() || self.nanos.is_negative()
}
}
impl SignedDuration {
pub(crate) fn zoned_until(
zoned1: &Zoned,
zoned2: &Zoned,
) -> SignedDuration {
SignedDuration::timestamp_until(zoned1.timestamp(), zoned2.timestamp())
}
pub(crate) fn timestamp_until(
timestamp1: Timestamp,
timestamp2: Timestamp,
) -> SignedDuration {
timestamp2.as_duration() - timestamp1.as_duration()
}
pub(crate) fn datetime_until(
datetime1: DateTime,
datetime2: DateTime,
) -> SignedDuration {
let date_until =
SignedDuration::date_until(datetime1.date(), datetime2.date());
let time_until =
SignedDuration::time_until(datetime1.time(), datetime2.time());
date_until + time_until
}
pub(crate) fn date_until(date1: Date, date2: Date) -> SignedDuration {
let days = date1.until_days(date2);
let hours = 24 * i64::from(days);
SignedDuration::from_hours(hours)
}
pub(crate) fn time_until(time1: Time, time2: Time) -> SignedDuration {
SignedDuration::from_nanos(time1.until_nanoseconds(time2))
}
pub(crate) fn offset_until(
offset1: Offset,
offset2: Offset,
) -> SignedDuration {
let secs1 = i64::from(offset1.seconds());
let secs2 = i64::from(offset2.seconds());
let diff = secs2 - secs1;
SignedDuration::from_secs(diff)
}
#[cfg(feature = "std")]
#[inline]
pub fn system_until(
time1: std::time::SystemTime,
time2: std::time::SystemTime,
) -> Result<SignedDuration, Error> {
match time2.duration_since(time1) {
Ok(dur) => {
SignedDuration::try_from(dur).context(E::ConvertSystemTime)
}
Err(err) => {
let dur = err.duration();
let dur = SignedDuration::try_from(dur)
.context(E::ConvertSystemTime)?;
dur.checked_neg()
.ok_or_else(b::SignedDurationSeconds::error)
.context(E::ConvertSystemTime)
}
}
}
}
impl SignedDuration {
#[inline]
pub fn round<R: Into<SignedDurationRound>>(
self,
options: R,
) -> Result<SignedDuration, Error> {
let options: SignedDurationRound = options.into();
options.round(self)
}
}
impl SignedDuration {
#[inline]
pub(crate) const fn from_civil_weeks32(weeks: i32) -> SignedDuration {
SignedDuration::from_secs(
(weeks as i64)
* DAYS_PER_WEEK
* HOURS_PER_CIVIL_DAY
* MINS_PER_HOUR
* SECS_PER_MINUTE,
)
}
#[inline]
pub(crate) const fn from_civil_days32(days: i32) -> SignedDuration {
SignedDuration::from_secs(
(days as i64)
* HOURS_PER_CIVIL_DAY
* MINS_PER_HOUR
* SECS_PER_MINUTE,
)
}
#[inline]
pub(crate) const fn from_hours32(hours: i32) -> SignedDuration {
SignedDuration::from_secs(
(hours as i64) * MINS_PER_HOUR * SECS_PER_MINUTE,
)
}
#[allow(dead_code)]
#[inline]
pub(crate) const fn from_mins32(mins: i32) -> SignedDuration {
SignedDuration::from_secs((mins as i64) * SECS_PER_MINUTE)
}
#[inline]
pub(crate) const fn as_civil_weeks(&self) -> i64 {
self.as_secs()
/ (DAYS_PER_WEEK
* HOURS_PER_CIVIL_DAY
* MINS_PER_HOUR
* SECS_PER_MINUTE)
}
#[inline]
pub(crate) const fn as_civil_days(&self) -> i64 {
self.as_secs()
/ (HOURS_PER_CIVIL_DAY * MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
pub(crate) fn as_civil_weeks_with_remainder(
&self,
) -> (i64, SignedDuration) {
let weeks = self.as_civil_weeks();
let secs = self.as_secs()
% (DAYS_PER_WEEK
* HOURS_PER_CIVIL_DAY
* MINS_PER_HOUR
* SECS_PER_MINUTE);
let rem = SignedDuration::new_unchecked(secs, self.subsec_nanos());
(weeks, rem)
}
#[inline]
pub(crate) fn as_civil_days_with_remainder(
&self,
) -> (i64, SignedDuration) {
let days = self.as_civil_days();
let secs = self.as_secs()
% (HOURS_PER_CIVIL_DAY * MINS_PER_HOUR * SECS_PER_MINUTE);
let rem = SignedDuration::new_unchecked(secs, self.subsec_nanos());
(days, rem)
}
#[inline]
pub(crate) fn as_hours_with_remainder(&self) -> (i64, SignedDuration) {
let hours = self.as_hours();
let secs = self.as_secs() % (MINS_PER_HOUR * SECS_PER_MINUTE);
let rem = SignedDuration::new_unchecked(secs, self.subsec_nanos());
(hours, rem)
}
#[inline]
pub(crate) fn as_mins_with_remainder(&self) -> (i64, SignedDuration) {
let mins = self.as_mins();
let secs = self.as_secs() % SECS_PER_MINUTE;
let rem = SignedDuration::new_unchecked(secs, self.subsec_nanos());
(mins, rem)
}
#[inline]
pub(crate) fn as_secs_with_remainder(&self) -> (i64, SignedDuration) {
let secs = self.as_secs();
let rem = SignedDuration::new_unchecked(0, self.subsec_nanos());
(secs, rem)
}
#[inline]
pub(crate) fn as_millis_with_remainder(&self) -> (i128, SignedDuration) {
let millis = self.as_millis();
let nanos = self.subsec_nanos() % NANOS_PER_MILLI;
let rem = SignedDuration::new_unchecked(0, nanos);
(millis, rem)
}
#[inline]
pub(crate) fn as_micros_with_remainder(&self) -> (i128, SignedDuration) {
let micros = self.as_micros();
let nanos = self.subsec_nanos() % NANOS_PER_MICRO;
let rem = SignedDuration::new_unchecked(0, nanos);
(micros, rem)
}
}
impl core::fmt::Display for SignedDuration {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
if f.alternate() {
friendly::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
} else {
temporal::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
}
impl core::fmt::Debug for SignedDuration {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
if f.alternate() {
if self.subsec_nanos() == 0 {
core::fmt::Display::fmt(&self.as_secs(), f)?;
f.write_str("s")
} else if self.as_secs() == 0 {
core::fmt::Display::fmt(&self.subsec_nanos(), f)?;
f.write_str("ns")
} else {
core::fmt::Display::fmt(&self.as_secs(), f)?;
f.write_str("s ")?;
core::fmt::Display::fmt(
&self.subsec_nanos().unsigned_abs(),
f,
)?;
f.write_str("ns")
}
} else {
friendly::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
}
impl TryFrom<Duration> for SignedDuration {
type Error = Error;
fn try_from(d: Duration) -> Result<SignedDuration, Error> {
let secs = i64::try_from(d.as_secs())
.map_err(|_| b::SignedDurationSeconds::error())?;
let nanos = i32::try_from(d.subsec_nanos()).unwrap();
Ok(SignedDuration::new_unchecked(secs, nanos))
}
}
impl TryFrom<SignedDuration> for Duration {
type Error = Error;
fn try_from(sd: SignedDuration) -> Result<Duration, Error> {
let secs = u64::try_from(sd.as_secs())
.map_err(|_| SpecialBoundsError::SignedToUnsignedDuration)?;
let nanos = u32::try_from(sd.subsec_nanos())
.map_err(|_| SpecialBoundsError::SignedToUnsignedDuration)?;
Ok(Duration::new(secs, nanos))
}
}
impl From<Offset> for SignedDuration {
fn from(offset: Offset) -> SignedDuration {
SignedDuration::from_secs(i64::from(offset.seconds()))
}
}
impl core::str::FromStr for SignedDuration {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<SignedDuration, Error> {
parse_iso_or_friendly(string.as_bytes())
}
}
impl core::ops::Neg for SignedDuration {
type Output = SignedDuration;
#[inline]
fn neg(self) -> SignedDuration {
self.checked_neg().expect("overflow when negating signed duration")
}
}
impl core::ops::Add for SignedDuration {
type Output = SignedDuration;
#[inline]
fn add(self, rhs: SignedDuration) -> SignedDuration {
self.checked_add(rhs).expect("overflow when adding signed durations")
}
}
impl core::ops::AddAssign for SignedDuration {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub for SignedDuration {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: SignedDuration) -> SignedDuration {
self.checked_sub(rhs)
.expect("overflow when subtracting signed durations")
}
}
impl core::ops::SubAssign for SignedDuration {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl core::ops::Mul<i32> for SignedDuration {
type Output = SignedDuration;
#[inline]
fn mul(self, rhs: i32) -> SignedDuration {
self.checked_mul(rhs)
.expect("overflow when multiplying signed duration by scalar")
}
}
impl core::iter::Sum for SignedDuration {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::new(0, 0), |acc, d| acc + d)
}
}
impl<'a> core::iter::Sum<&'a Self> for SignedDuration {
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
iter.fold(Self::new(0, 0), |acc, d| acc + *d)
}
}
impl core::ops::Mul<SignedDuration> for i32 {
type Output = SignedDuration;
#[inline]
fn mul(self, rhs: SignedDuration) -> SignedDuration {
rhs * self
}
}
impl core::ops::MulAssign<i32> for SignedDuration {
#[inline]
fn mul_assign(&mut self, rhs: i32) {
*self = *self * rhs;
}
}
impl core::ops::Div<i32> for SignedDuration {
type Output = SignedDuration;
#[inline]
fn div(self, rhs: i32) -> SignedDuration {
self.checked_div(rhs)
.expect("overflow when dividing signed duration by scalar")
}
}
impl core::ops::DivAssign<i32> for SignedDuration {
#[inline]
fn div_assign(&mut self, rhs: i32) {
*self = *self / rhs;
}
}
#[cfg(feature = "serde")]
impl serde_core::Serialize for SignedDuration {
#[inline]
fn serialize<S: serde_core::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde_core::Deserialize<'de> for SignedDuration {
#[inline]
fn deserialize<D: serde_core::Deserializer<'de>>(
deserializer: D,
) -> Result<SignedDuration, D::Error> {
use serde_core::de;
struct SignedDurationVisitor;
impl<'de> de::Visitor<'de> for SignedDurationVisitor {
type Value = SignedDuration;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a signed duration string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<SignedDuration, E> {
parse_iso_or_friendly(value).map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(
self,
value: &str,
) -> Result<SignedDuration, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_str(SignedDurationVisitor)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SignedDurationRound {
smallest: Unit,
mode: RoundMode,
increment: i64,
}
impl SignedDurationRound {
#[inline]
pub fn new() -> SignedDurationRound {
SignedDurationRound {
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> SignedDurationRound {
SignedDurationRound { smallest: unit, ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> SignedDurationRound {
SignedDurationRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> SignedDurationRound {
SignedDurationRound { increment, ..self }
}
fn round(&self, dur: SignedDuration) -> Result<SignedDuration, Error> {
let increment =
Increment::for_signed_duration(self.smallest, self.increment)?;
increment
.round(self.mode, dur)
.with_context(|| E::RoundOverflowed { unit: self.smallest })
}
}
impl Default for SignedDurationRound {
fn default() -> SignedDurationRound {
SignedDurationRound::new()
}
}
impl From<Unit> for SignedDurationRound {
fn from(unit: Unit) -> SignedDurationRound {
SignedDurationRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for SignedDurationRound {
fn from((unit, increment): (Unit, i64)) -> SignedDurationRound {
SignedDurationRound::default().smallest(unit).increment(increment)
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse_iso_or_friendly(bytes: &[u8]) -> Result<SignedDuration, Error> {
let Some((&byte, tail)) = bytes.split_first() else {
return Err(crate::Error::from(
crate::error::fmt::Error::HybridDurationEmpty,
));
};
let mut first = byte;
if first == b'+' || first == b'-' {
let Some(&byte) = tail.first() else {
return Err(crate::Error::from(
crate::error::fmt::Error::HybridDurationPrefix { sign: first },
));
};
first = byte;
}
if first == b'P' || first == b'p' {
temporal::DEFAULT_SPAN_PARSER.parse_duration(bytes)
} else {
friendly::DEFAULT_SPAN_PARSER.parse_duration(bytes)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use alloc::string::ToString;
use super::*;
#[test]
fn new() {
let d = SignedDuration::new(12, i32::MAX);
assert_eq!(d.as_secs(), 14);
assert_eq!(d.subsec_nanos(), 147_483_647);
let d = SignedDuration::new(-12, i32::MIN);
assert_eq!(d.as_secs(), -14);
assert_eq!(d.subsec_nanos(), -147_483_648);
let d = SignedDuration::new(i64::MAX, i32::MIN);
assert_eq!(d.as_secs(), i64::MAX - 3);
assert_eq!(d.subsec_nanos(), 852_516_352);
let d = SignedDuration::new(i64::MIN, i32::MAX);
assert_eq!(d.as_secs(), i64::MIN + 3);
assert_eq!(d.subsec_nanos(), -852_516_353);
}
#[test]
#[should_panic]
fn new_fail_positive() {
SignedDuration::new(i64::MAX, 1_000_000_000);
}
#[test]
#[should_panic]
fn new_fail_negative() {
SignedDuration::new(i64::MIN, -1_000_000_000);
}
#[test]
fn from_hours_limits() {
let d = SignedDuration::from_hours(2_562_047_788_015_215);
assert_eq!(d.as_secs(), 9223372036854774000);
let d = SignedDuration::from_hours(-2_562_047_788_015_215);
assert_eq!(d.as_secs(), -9223372036854774000);
}
#[test]
#[should_panic]
fn from_hours_fail_positive() {
SignedDuration::from_hours(2_562_047_788_015_216);
}
#[test]
#[should_panic]
fn from_hours_fail_negative() {
SignedDuration::from_hours(-2_562_047_788_015_216);
}
#[test]
fn from_minutes_limits() {
let d = SignedDuration::from_mins(153_722_867_280_912_930);
assert_eq!(d.as_secs(), 9223372036854775800);
let d = SignedDuration::from_mins(-153_722_867_280_912_930);
assert_eq!(d.as_secs(), -9223372036854775800);
}
#[test]
#[should_panic]
fn from_minutes_fail_positive() {
SignedDuration::from_mins(153_722_867_280_912_931);
}
#[test]
#[should_panic]
fn from_minutes_fail_negative() {
SignedDuration::from_mins(-153_722_867_280_912_931);
}
#[test]
fn add() {
let add = |(secs1, nanos1): (i64, i32),
(secs2, nanos2): (i64, i32)|
-> (i64, i32) {
let d1 = SignedDuration::new(secs1, nanos1);
let d2 = SignedDuration::new(secs2, nanos2);
let sum = d1.checked_add(d2).unwrap();
(sum.as_secs(), sum.subsec_nanos())
};
assert_eq!(add((1, 1), (1, 1)), (2, 2));
assert_eq!(add((1, 1), (-1, -1)), (0, 0));
assert_eq!(add((-1, -1), (1, 1)), (0, 0));
assert_eq!(add((-1, -1), (-1, -1)), (-2, -2));
assert_eq!(add((1, 500_000_000), (1, 500_000_000)), (3, 0));
assert_eq!(add((-1, -500_000_000), (-1, -500_000_000)), (-3, 0));
assert_eq!(
add((5, 200_000_000), (-1, -500_000_000)),
(3, 700_000_000)
);
assert_eq!(
add((-5, -200_000_000), (1, 500_000_000)),
(-3, -700_000_000)
);
}
#[test]
fn add_overflow() {
let add = |(secs1, nanos1): (i64, i32),
(secs2, nanos2): (i64, i32)|
-> Option<(i64, i32)> {
let d1 = SignedDuration::new(secs1, nanos1);
let d2 = SignedDuration::new(secs2, nanos2);
d1.checked_add(d2).map(|d| (d.as_secs(), d.subsec_nanos()))
};
assert_eq!(None, add((i64::MAX, 0), (1, 0)));
assert_eq!(None, add((i64::MIN, 0), (-1, 0)));
assert_eq!(None, add((i64::MAX, 1), (0, 999_999_999)));
assert_eq!(None, add((i64::MIN, -1), (0, -999_999_999)));
}
#[test]
fn signed_duration_deserialize_yaml() {
let expected = SignedDuration::from_secs(123456789);
let deserialized: SignedDuration =
serde_yaml::from_str("PT34293h33m9s").unwrap();
assert_eq!(deserialized, expected);
let deserialized: SignedDuration =
serde_yaml::from_slice("PT34293h33m9s".as_bytes()).unwrap();
assert_eq!(deserialized, expected);
let cursor = Cursor::new(b"PT34293h33m9s");
let deserialized: SignedDuration =
serde_yaml::from_reader(cursor).unwrap();
assert_eq!(deserialized, expected);
}
#[test]
fn from_str() {
let p = |s: &str| -> Result<SignedDuration, Error> { s.parse() };
insta::assert_snapshot!(
p("1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-1 hour").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-PT1h").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@r#"an empty string is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@r#"found nothing after sign `+`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@r#"found nothing after sign `-`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
}
#[test]
fn serde_deserialize() {
let p = |s: &str| -> Result<SignedDuration, serde_json::Error> {
serde_json::from_str(&alloc::format!("\"{s}\""))
};
insta::assert_snapshot!(
p("1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-1 hour").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-PT1h").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@r#"an empty string is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 2"#,
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@r#"found nothing after sign `+`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 3"#,
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@r#"found nothing after sign `-`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 3"#,
);
}
#[test]
fn humantime_compatibility_parse() {
let dur = std::time::Duration::new(26_784, 123_456_789);
let formatted = humantime::format_duration(dur).to_string();
assert_eq!(formatted, "7h 26m 24s 123ms 456us 789ns");
let expected = SignedDuration::try_from(dur).unwrap();
assert_eq!(formatted.parse::<SignedDuration>().unwrap(), expected);
}
#[test]
fn humantime_compatibility_print() {
static PRINTER: friendly::SpanPrinter = friendly::SpanPrinter::new()
.designator(friendly::Designator::HumanTime);
let sdur = SignedDuration::new(26_784, 123_456_789);
let formatted = PRINTER.duration_to_string(&sdur);
assert_eq!(formatted, "7h 26m 24s 123ms 456us 789ns");
let dur = humantime::parse_duration(&formatted).unwrap();
let expected = std::time::Duration::try_from(sdur).unwrap();
assert_eq!(dur, expected);
}
#[test]
fn using_sum() {
let signed_durations = [
SignedDuration::new(12, 600_000_000),
SignedDuration::new(13, 400_000_000),
];
let sum1: SignedDuration = signed_durations.iter().sum();
let sum2: SignedDuration = signed_durations.into_iter().sum();
assert_eq!(sum1, SignedDuration::new(26, 0));
assert_eq!(sum2, SignedDuration::new(26, 0));
}
#[test]
#[should_panic]
fn using_sum_when_max_exceeds() {
[
SignedDuration::new(i64::MAX, 0),
SignedDuration::new(0, 1_000_000_000),
]
.iter()
.sum::<SignedDuration>();
}
#[test]
fn panic_try_from_secs_f64() {
let sdur = SignedDuration::try_from_secs_f64(0.999999999999).unwrap();
assert_eq!(sdur, SignedDuration::from_secs(1));
let sdur = SignedDuration::try_from_secs_f64(-0.999999999999).unwrap();
assert_eq!(sdur, SignedDuration::from_secs(-1));
let max = 9223372036854775807.999999999f64;
let sdur = SignedDuration::try_from_secs_f64(max).unwrap();
assert_eq!(sdur, SignedDuration::new(9223372036854775807, 0));
let min = -9223372036854775808.999999999f64;
let sdur = SignedDuration::try_from_secs_f64(min).unwrap();
assert_eq!(sdur, SignedDuration::new(-9223372036854775808, 0));
}
#[test]
fn panic_try_from_secs_f32() {
let sdur = SignedDuration::try_from_secs_f32(0.999999999).unwrap();
assert_eq!(sdur, SignedDuration::from_secs(1));
let sdur = SignedDuration::try_from_secs_f32(-0.999999999).unwrap();
assert_eq!(sdur, SignedDuration::from_secs(-1));
let x: f32 = 1.0;
let y: f32 = 0.999999999;
assert_eq!(x, y);
assert_eq!(y.fract(), 0.0f32);
}
#[test]
fn as_hours_with_remainder() {
let sdur = SignedDuration::new(4 * 60 * 60 + 30 * 60, 123_000_000);
let (hours, rem) = sdur.as_hours_with_remainder();
assert_eq!(hours, 4);
assert_eq!(rem, SignedDuration::new(30 * 60, 123_000_000));
let sdur = SignedDuration::new(-(4 * 60 * 60 + 30 * 60), -123_000_000);
let (hours, rem) = sdur.as_hours_with_remainder();
assert_eq!(hours, -4);
assert_eq!(rem, SignedDuration::new(-30 * 60, -123_000_000));
}
#[test]
fn as_mins_with_remainder() {
let sdur = SignedDuration::new(4 * 60 + 30, 123_000_000);
let (mins, rem) = sdur.as_mins_with_remainder();
assert_eq!(mins, 4);
assert_eq!(rem, SignedDuration::new(30, 123_000_000));
let sdur = SignedDuration::new(-(4 * 60 + 30), -123_000_000);
let (mins, rem) = sdur.as_mins_with_remainder();
assert_eq!(mins, -4);
assert_eq!(rem, SignedDuration::new(-30, -123_000_000));
}
#[test]
fn as_secs_with_remainder() {
let sdur = SignedDuration::new(4, 123_456_789);
let (secs, rem) = sdur.as_secs_with_remainder();
assert_eq!(secs, 4);
assert_eq!(rem, SignedDuration::new(0, 123_456_789));
let sdur = SignedDuration::new(-4, -123_456_789);
let (secs, rem) = sdur.as_secs_with_remainder();
assert_eq!(secs, -4);
assert_eq!(rem, SignedDuration::new(0, -123_456_789));
}
#[test]
fn as_millis_with_remainder() {
let sdur = SignedDuration::new(4, 123_456_789);
let (millis, rem) = sdur.as_millis_with_remainder();
assert_eq!(millis, 4_123);
assert_eq!(rem, SignedDuration::new(0, 000_456_789));
let sdur = SignedDuration::new(-4, -123_456_789);
let (millis, rem) = sdur.as_millis_with_remainder();
assert_eq!(millis, -4_123);
assert_eq!(rem, SignedDuration::new(0, -000_456_789));
}
#[test]
fn as_micros_with_remainder() {
let sdur = SignedDuration::new(4, 123_456_789);
let (micros, rem) = sdur.as_micros_with_remainder();
assert_eq!(micros, 4_123_456);
assert_eq!(rem, SignedDuration::new(0, 000_000_789));
let sdur = SignedDuration::new(-4, -123_456_789);
let (micros, rem) = sdur.as_micros_with_remainder();
assert_eq!(micros, -4_123_456);
assert_eq!(rem, SignedDuration::new(0, -000_000_789));
}
}