use core::time::Duration;
use crate::{
civil::{Date, DateTime, Time},
error::{err, ErrorContext},
tz::Offset,
Error, Timestamp, Zoned,
};
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SignedDuration {
secs: i64,
nanos: i32,
}
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;
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_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_nanos(nanos: i64) -> SignedDuration {
let secs = nanos / (NANOS_PER_SEC as i64);
let nanos = (nanos % (NANOS_PER_SEC as i64)) as i32;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_hours(hours: i64) -> 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 hours < MIN_HOUR {
panic!("hours overflowed minimum number of SignedDuration seconds")
}
if hours > MAX_HOUR {
panic!("hours overflowed maximum number of SignedDuration seconds")
}
SignedDuration::from_secs(hours * MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
pub const fn from_mins(minutes: i64) -> SignedDuration {
const MIN_MINUTE: i64 = i64::MIN / SECS_PER_MINUTE;
const MAX_MINUTE: i64 = i64::MAX / SECS_PER_MINUTE;
if minutes < MIN_MINUTE {
panic!(
"minutes overflowed minimum number of SignedDuration seconds"
)
}
if minutes > MAX_MINUTE {
panic!(
"minutes overflowed maximum number of SignedDuration seconds"
)
}
SignedDuration::from_secs(minutes * SECS_PER_MINUTE)
}
pub(crate) fn from_timestamp(timestamp: Timestamp) -> SignedDuration {
SignedDuration::new_unchecked(
timestamp.as_second(),
timestamp.subsec_nanosecond(),
)
}
#[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 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 >= 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> {
if !secs.is_finite() {
return Err(err!(
"could not convert non-finite seconds \
{secs} to signed duration",
));
}
if secs < (i64::MIN as f64) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
minimum value of {:?}",
SignedDuration::MIN,
));
}
if secs > (i64::MAX as f64) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
maximum value of {:?}",
SignedDuration::MAX,
));
}
let nanos = (secs.fract() * (NANOS_PER_SEC as f64)).round() as i32;
let secs = secs.trunc() as i64;
Ok(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub fn try_from_secs_f32(secs: f32) -> Result<SignedDuration, Error> {
if !secs.is_finite() {
return Err(err!(
"could not convert non-finite seconds \
{secs} to signed duration",
));
}
if secs < (i64::MIN as f32) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
minimum value of {:?}",
SignedDuration::MIN,
));
}
if secs > (i64::MAX as f32) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
maximum value of {:?}",
SignedDuration::MAX,
));
}
let nanos = (secs.fract() * (NANOS_PER_SEC as f32)).round() as i32;
let secs = secs.trunc() as i64;
Ok(SignedDuration::new_unchecked(secs, 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 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_jiff_duration() - timestamp1.as_jiff_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_ranged(date2);
let hours = 24 * i64::from(days.get());
SignedDuration::from_hours(hours)
}
pub(crate) fn time_until(time1: Time, time2: Time) -> SignedDuration {
let nanos = time1.until_nanoseconds(time2);
SignedDuration::from_nanos(nanos.get())
}
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).with_context(|| {
err!(
"unsigned duration {dur:?} for system time since \
Unix epoch overflowed signed duration"
)
}),
Err(err) => {
let dur = err.duration();
let dur =
SignedDuration::try_from(dur).with_context(|| {
err!(
"unsigned duration {dur:?} for system time before \
Unix epoch overflowed signed duration"
)
})?;
dur.checked_neg().ok_or_else(|| {
err!("negating duration {dur:?} from before the Unix epoch \
overflowed signed duration")
})
}
}
}
}
impl core::fmt::Display for SignedDuration {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::{temporal::DEFAULT_SPAN_PRINTER, StdFmtWrite};
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 {
if self.nanos == 0 {
write!(f, "{}s", self.secs)
} else if self.secs == 0 {
write!(f, "{}ns", self.nanos)
} else {
write!(f, "{}s{}ns", self.secs, self.nanos.abs())
}
}
}
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(|_| {
err!("seconds in unsigned duration {d:?} overflowed i64")
})?;
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(|_| {
err!("seconds in signed duration {sd:?} overflowed u64")
})?;
let nanos = u32::try_from(sd.subsec_nanos()).unwrap();
Ok(Duration::new(secs, nanos))
}
}
impl core::str::FromStr for SignedDuration {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<SignedDuration, Error> {
use crate::fmt::temporal::DEFAULT_SPAN_PARSER;
DEFAULT_SPAN_PARSER.parse_duration(string)
}
}
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::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::Serialize for SignedDuration {
#[inline]
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SignedDuration {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<SignedDuration, D::Error> {
use serde::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> {
use crate::fmt::temporal::DEFAULT_SPAN_PARSER;
DEFAULT_SPAN_PARSER
.parse_duration(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_bytes(SignedDurationVisitor)
}
}
#[cfg(test)]
mod tests {
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)));
}
}