use core::time::Duration;
use tz::datetime::DateTime;
use super::error::{IntOverflowError, TzOutOfRangeError};
use super::{Time, TimeError};
use crate::NANOS_IN_SECOND;
impl Time {
#[inline]
#[allow(clippy::missing_panics_doc)]
pub fn round(&self, ndigits: u32) -> Self {
match ndigits {
9..=u32::MAX => *self,
num_digits => {
let local_time_type = *self.inner.local_time_type();
let mut unix_time = self.to_int();
let nanos = self.nanoseconds();
let truncating_divisor = 10_u64.pow(9 - num_digits - 1);
let rounding_multiple = 10_u64.pow(9 - num_digits);
let truncated = u64::from(nanos) / truncating_divisor;
let mut new_nanos = if truncated % 10 >= 5 {
((truncated / 10) + 1) * rounding_multiple
} else {
(truncated / 10) * rounding_multiple
}
.try_into()
.expect("new nanos are a truncated version of input which is in bounds for u32");
if new_nanos >= NANOS_IN_SECOND {
unix_time += 1;
new_nanos -= NANOS_IN_SECOND;
}
let dt = DateTime::from_timespec_and_local(unix_time, new_nanos, local_time_type)
.expect("Could not round the datetime");
Self {
inner: dt,
offset: self.offset,
}
}
}
}
}
impl Time {
pub fn checked_add(self, duration: Duration) -> Result<Self, TimeError> {
let unix_time = self.inner.unix_time();
let nanoseconds = self.inner.nanoseconds();
let offset = self.offset;
let duration_seconds = i64::try_from(duration.as_secs())?;
let duration_subsecs = duration.subsec_nanos();
let mut seconds = unix_time.checked_add(duration_seconds).ok_or(IntOverflowError::new())?;
let mut nanoseconds = nanoseconds
.checked_add(duration_subsecs)
.ok_or(IntOverflowError::new())?;
if nanoseconds > NANOS_IN_SECOND {
seconds += 1;
nanoseconds -= NANOS_IN_SECOND;
}
Self::with_timespec_and_offset(seconds, nanoseconds, offset)
}
pub fn checked_add_i64(&self, seconds: i64) -> Result<Self, TimeError> {
if seconds.is_negative() {
let seconds = seconds
.checked_neg()
.and_then(|secs| u64::try_from(secs).ok())
.ok_or(IntOverflowError::new())?;
self.checked_sub_u64(seconds)
} else {
let seconds = u64::try_from(seconds).map_err(|_| IntOverflowError::new())?;
self.checked_add_u64(seconds)
}
}
pub fn checked_add_u64(&self, seconds: u64) -> Result<Self, TimeError> {
let duration = Duration::from_secs(seconds);
self.checked_add(duration)
}
pub fn checked_add_f64(&self, seconds: f64) -> Result<Self, TimeError> {
if seconds.is_nan() || seconds.is_infinite() {
return Err(TzOutOfRangeError::new().into());
}
if seconds.is_sign_positive() {
self.checked_add(Duration::try_from_secs_f64(seconds)?)
} else {
self.checked_sub(Duration::try_from_secs_f64(-seconds)?)
}
}
}
impl Time {
pub fn checked_sub(self, duration: Duration) -> Result<Self, TimeError> {
let unix_time = self.inner.unix_time();
let nanoseconds = self.inner.nanoseconds();
let offset = self.offset;
let duration_seconds = i64::try_from(duration.as_secs())?;
let duration_subsecs = duration.subsec_nanos();
let mut seconds = unix_time.checked_sub(duration_seconds).ok_or(IntOverflowError::new())?;
let nanoseconds = if let Some(nanos) = nanoseconds.checked_sub(duration_subsecs) {
nanos
} else {
seconds -= 1;
nanoseconds + NANOS_IN_SECOND - duration_subsecs
};
Self::with_timespec_and_offset(seconds, nanoseconds, offset)
}
pub fn checked_sub_i64(self, seconds: i64) -> Result<Self, TimeError> {
if seconds.is_negative() {
let seconds = seconds
.checked_neg()
.and_then(|secs| u64::try_from(secs).ok())
.ok_or(IntOverflowError::new())?;
self.checked_add_u64(seconds)
} else {
let seconds = u64::try_from(seconds).map_err(|_| IntOverflowError::new())?;
self.checked_sub_u64(seconds)
}
}
pub fn checked_sub_u64(self, seconds: u64) -> Result<Self, TimeError> {
let duration = Duration::from_secs(seconds);
self.checked_sub(duration)
}
pub fn checked_sub_f64(self, seconds: f64) -> Result<Self, TimeError> {
if seconds.is_nan() || seconds.is_infinite() {
return Err(TzOutOfRangeError::new().into());
}
if seconds.is_sign_positive() {
self.checked_sub(Duration::try_from_secs_f64(seconds)?)
} else {
self.checked_add(Duration::try_from_secs_f64(-seconds)?)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn datetime() -> Time {
Time::utc(2019, 4, 7, 23, 59, 59, 500_000_000).unwrap()
}
#[test]
fn rounding() {
let dt = Time::utc(2010, 3, 30, 5, 43, 25, 123_456_789).unwrap();
assert_eq!(0, dt.round(0).nanoseconds());
assert_eq!(100_000_000, dt.round(1).nanoseconds());
assert_eq!(120_000_000, dt.round(2).nanoseconds());
assert_eq!(123_000_000, dt.round(3).nanoseconds());
assert_eq!(123_500_000, dt.round(4).nanoseconds());
assert_eq!(123_460_000, dt.round(5).nanoseconds());
assert_eq!(123_457_000, dt.round(6).nanoseconds());
assert_eq!(123_456_800, dt.round(7).nanoseconds());
assert_eq!(123_456_790, dt.round(8).nanoseconds());
assert_eq!(123_456_789, dt.round(9).nanoseconds());
assert_eq!(123_456_789, dt.round(10).nanoseconds());
assert_eq!(123_456_789, dt.round(11).nanoseconds());
}
#[test]
fn rounding_rollup() {
let dt = Time::utc(1999, 12, 31, 23, 59, 59, 900_000_000).unwrap();
let rounded = dt.round(0);
let dt_unix = dt.to_int();
let rounded_unix = rounded.to_int();
assert_eq!(0, rounded.nanoseconds());
assert_eq!(dt_unix + 1, rounded_unix);
}
#[test]
fn add_int_to_time() {
let dt = datetime();
let succ: Time = dt.checked_add_u64(1).unwrap();
assert_eq!(dt.to_int() + 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_ne!(dt.day(), succ.day());
assert_ne!(dt.hour(), succ.hour());
assert_ne!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 0);
if succ.nanoseconds() > 500_000_000 {
assert!(succ.nanoseconds() - 500_000_000 < 50);
} else {
assert!(500_000_000 - succ.nanoseconds() < 50);
}
}
#[test]
fn add_out_of_fixnum_range_float_sec() {
let dt = datetime();
dt.checked_add_f64(f64::MAX).unwrap_err();
let dt = datetime();
dt.checked_add_f64(f64::MIN).unwrap_err();
}
#[test]
fn add_subsec_float_to_time() {
let dt = datetime();
let succ: Time = dt.checked_add_f64(0.2).unwrap();
assert_eq!(dt.to_int(), succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 59);
if succ.nanoseconds() > 700_000_000 {
assert!(succ.nanoseconds() - 700_000_000 < 50);
} else {
assert!(700_000_000 - succ.nanoseconds() < 50);
}
let dt = datetime();
let succ: Time = dt.checked_add_f64(0.7).unwrap();
assert_eq!(dt.to_int() + 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_ne!(dt.day(), succ.day());
assert_ne!(dt.hour(), succ.hour());
assert_ne!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 0);
if succ.nanoseconds() > 200_000_000 {
assert!(succ.nanoseconds() - 200_000_000 < 50);
} else {
assert!(200_000_000 - succ.nanoseconds() < 50);
}
}
#[test]
fn add_float_to_time() {
let dt = datetime();
let succ: Time = dt.checked_add_f64(1.2).unwrap();
assert_eq!(dt.to_int() + 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_ne!(dt.day(), succ.day());
assert_ne!(dt.hour(), succ.hour());
assert_ne!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 0);
if succ.nanoseconds() > 700_000_000 {
assert!(succ.nanoseconds() - 700_000_000 < 50);
} else {
assert!(700_000_000 - succ.nanoseconds() < 50);
}
let dt = datetime();
let succ: Time = dt.checked_add_f64(1.7).unwrap();
assert_eq!(dt.to_int() + 2, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_ne!(dt.day(), succ.day());
assert_ne!(dt.hour(), succ.hour());
assert_ne!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 1);
if succ.nanoseconds() > 200_000_000 {
assert!(succ.nanoseconds() - 200_000_000 < 50);
} else {
assert!(200_000_000 - succ.nanoseconds() < 50);
}
}
#[test]
fn sub_int_to_time() {
let dt = datetime();
let succ: Time = dt.checked_sub_u64(1).unwrap();
assert_eq!(dt.to_int() - 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 58);
if succ.nanoseconds() > 500_000_000 {
assert!(succ.nanoseconds() - 500_000_000 < 50);
} else {
assert!(500_000_000 - succ.nanoseconds() < 50);
}
}
#[test]
fn sub_out_of_fixnum_range_float_sec() {
let dt = datetime();
dt.checked_sub_f64(f64::MAX).unwrap_err();
let dt = datetime();
dt.checked_sub_f64(f64::MIN).unwrap_err();
}
#[test]
fn sub_subsec_float_to_time() {
let dt = datetime();
let succ: Time = dt.checked_sub_f64(0.2).unwrap();
assert_eq!(dt.to_int(), succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 59);
if succ.nanoseconds() > 300_000_000 {
assert!(succ.nanoseconds() - 300_000_000 < 50);
} else {
assert!(300_000_000 - succ.nanoseconds() < 50);
}
let dt = datetime();
let succ: Time = dt.checked_sub_f64(0.7).unwrap();
assert_eq!(dt.to_int() - 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 58);
if succ.nanoseconds() > 800_000_000 {
assert!(succ.nanoseconds() - 800_000_000 < 50);
} else {
assert!(800_000_000 - succ.nanoseconds() < 50);
}
}
#[test]
fn sub_float_to_time() {
let dt = datetime();
let succ: Time = dt.checked_sub_f64(1.2).unwrap();
assert_eq!(dt.to_int() - 1, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 58);
if succ.nanoseconds() > 300_000_000 {
assert!(succ.nanoseconds() - 300_000_000 < 50);
} else {
assert!(300_000_000 - succ.nanoseconds() < 50);
}
let dt = datetime();
let succ: Time = dt.checked_sub_f64(1.7).unwrap();
assert_eq!(dt.to_int() - 2, succ.to_int());
assert_eq!(dt.year(), succ.year());
assert_eq!(dt.month(), succ.month());
assert_eq!(dt.day(), succ.day());
assert_eq!(dt.hour(), succ.hour());
assert_eq!(dt.minute(), succ.minute());
assert_eq!(succ.second(), 57);
if succ.nanoseconds() > 800_000_000 {
assert!(succ.nanoseconds() - 800_000_000 < 50);
} else {
assert!(800_000_000 - succ.nanoseconds() < 50);
}
}
}