diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1fc9e3dce6..03f2650adf 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -13,6 +13,7 @@ use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; @@ -314,12 +315,24 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[deprecated(since = "0.4.24", note = "Use checked_add() instead")] + #[allow(deprecated)] pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { let datetime = self.datetime.checked_add_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } + /// Adds given `Duration` to the current date and time. + /// + /// Returns `None` when it will result in overflow. + #[inline] + pub fn checked_add(self, rhs: Duration) -> Option> { + let datetime = self.datetime.checked_add(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + /// Adds given `Months` to the current date and time. /// /// Returns `None` when it will result in overflow, or if the @@ -337,12 +350,24 @@ impl DateTime { /// /// Returns `None` when it will result in overflow. #[inline] + #[deprecated(since = "0.4.24", note = "Use checked_sub() instead")] + #[allow(deprecated)] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { let datetime = self.datetime.checked_sub_signed(rhs)?; let tz = self.timezone(); Some(tz.from_utc_datetime(&datetime)) } + /// Subtracts given `Duration` from the current date and time. + /// + /// Returns `None` when it will result in overflow. + #[inline] + pub fn checked_sub(self, rhs: Duration) -> Option> { + let datetime = self.datetime.checked_sub(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + /// Subtracts given `Months` from the current date and time. /// /// Returns `None` when it will result in overflow, or if the @@ -379,10 +404,29 @@ impl DateTime { /// Subtracts another `DateTime` from the current date and time. /// This does not overflow or underflow at all. #[inline] + #[deprecated(since = "0.4.24", note = "Use checked_duration_since() or abs_diff() instead")] + #[allow(deprecated)] pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { self.datetime.signed_duration_since(rhs.datetime) } + /// Subtracts another `DateTime` from the current date and time. + /// This does not overflow or underflow at all. + #[inline] + pub fn checked_duration_since( + self, + rhs: DateTime, + ) -> Result { + self.datetime.checked_duration_since(rhs.datetime) + } + + /// Subtracts another `DateTime` from the current date and time. + /// This does not overflow or underflow at all. + #[inline] + pub fn abs_diff(self, rhs: DateTime) -> Duration { + self.datetime.abs_diff(rhs.datetime) + } + /// Returns a view to the naive UTC datetime. #[inline] pub fn naive_utc(&self) -> NaiveDateTime { @@ -903,6 +947,7 @@ impl hash::Hash for DateTime { } } +#[allow(deprecated)] impl Add for DateTime { type Output = DateTime; @@ -912,6 +957,7 @@ impl Add for DateTime { } } +#[allow(deprecated)] impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -922,6 +968,24 @@ impl AddAssign for DateTime { } } +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: Duration) -> DateTime { + self.checked_add(rhs).expect("`DateTime + Duration` overflowed") + } +} + +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + let datetime = self.datetime.checked_add(rhs).expect("`DateTime + Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime); + } +} + impl Add for DateTime { type Output = DateTime; @@ -930,6 +994,7 @@ impl Add for DateTime { } } +#[allow(deprecated)] impl Sub for DateTime { type Output = DateTime; @@ -939,6 +1004,7 @@ impl Sub for DateTime { } } +#[allow(deprecated)] impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -949,6 +1015,24 @@ impl SubAssign for DateTime { } } +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: Duration) -> DateTime { + self.checked_sub(rhs).expect("`DateTime - Duration` overflowed") + } +} + +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + let datetime = self.datetime.checked_sub(rhs).expect("`DateTime - Duration` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime) + } +} + impl Sub for DateTime { type Output = DateTime; @@ -957,6 +1041,7 @@ impl Sub for DateTime { } } +#[allow(deprecated)] impl Sub> for DateTime { type Output = OldDuration; @@ -1073,8 +1158,6 @@ impl From for DateTime { #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { - use std::time::Duration; - let sec = dt.timestamp(); let nsec = dt.timestamp_subsec_nanos(); if sec < 0 { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 879e2baa0a..6434a38a4f 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,3 +1,4 @@ +use core::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; use super::DateTime; @@ -5,11 +6,12 @@ use crate::naive::{NaiveDate, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, TimeZone, Utc}; -use crate::oldtime::Duration; +use crate::oldtime::Duration as OldDuration; #[cfg(feature = "clock")] use crate::Datelike; #[test] +#[allow(deprecated)] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); @@ -69,12 +71,87 @@ fn test_datetime_offset() { let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); assert_eq!( - dt + Duration::seconds(3600 + 60 + 1), + dt + OldDuration::seconds(3600 + 60 + 1), Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() ); assert_eq!( dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), - Duration::seconds(-7 * 3600 - 3 * 60 - 3) + OldDuration::seconds(-7 * 3600 - 3 * 60 - 3) + ); + + assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); + assert_eq!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), edt); + assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); +} + +#[test] +fn test_datetime_offset_duration() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 UTC" + ); + assert_eq!( + format!("{}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 -04:00" + ); + assert_eq!( + format!("{}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06 07:08:09 +09:00" + ); + assert_eq!( + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09-04:00" + ); + assert_eq!( + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap()), + "2014-05-06T07:08:09+09:00" + ); + + // edge cases + assert_eq!( + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00-04:00" + ); + assert_eq!( + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(0, 0, 0).unwrap()), + "2014-05-06T00:00:00+09:00" + ); + assert_eq!( + format!("{:?}", Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + "2014-05-06T23:59:59Z" + ); + assert_eq!( + format!("{:?}", edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + "2014-05-06T23:59:59-04:00" + ); + assert_eq!( + format!("{:?}", kst.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(23, 59, 59).unwrap()), + "2014-05-06T23:59:59+09:00" + ); + + let dt = Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(7, 8, 9).unwrap(); + assert_eq!(dt, edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(3, 8, 9).unwrap()); + assert_eq!( + dt + Duration::from_secs(3600 + 60 + 1), + Utc.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(8, 9, 10).unwrap() + ); + assert_eq!( + dt.checked_duration_since( + edt.ymd_opt(2014, 5, 6).unwrap().and_hms_opt(10, 11, 12).unwrap() + ), + Err(Duration::from_secs(7 * 3600 + 3 * 60 + 3)) ); assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); @@ -500,8 +577,6 @@ fn test_from_system_time() { #[test] #[cfg(target_os = "windows")] fn test_from_system_time() { - use std::time::Duration; - let nanos = 999_999_000; let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); @@ -627,43 +702,88 @@ fn test_datetime_from_local() { #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { + use crate::Days; + const WEEKS_PER_YEAR: f32 = 52.1775; // This is always at least one year because 1 year = 52.1775 weeks. let one_year_ago = - Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + Utc::now().date_naive() - Days::new((WEEKS_PER_YEAR * 1.5 * 7.0).ceil() as u64); // A bit more than 2 years. let two_year_ago = - Utc::now().date_naive() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + Utc::now().date_naive() - Days::new((WEEKS_PER_YEAR * 2.5 * 7.0).ceil() as u64); assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); // If the given DateTime is later than now, the function will always return 0. - let future = Utc::now().date_naive() + Duration::weeks(12); + let future = Utc::now().date_naive() + Days::new(12 * 7); assert_eq!(Utc::now().date_naive().years_since(future), None); } +#[test] +#[cfg(feature = "clock")] +fn test_years_elapsed_duration() { + const WEEKS_PER_YEAR: f32 = 52.1775; + + // This is always at least one year because 1 year = 52.1775 weeks. + let one_year_ago = Utc::today().and_hms_opt(0, 0, 0).unwrap() + - Duration::from_secs(7 * 24 * 60 * 60 * (WEEKS_PER_YEAR * 1.5).ceil() as u64); + // A bit more than 2 years. + let two_year_ago = Utc::today().and_hms_opt(0, 0, 0).unwrap() + - Duration::from_secs(7 * 24 * 60 * 60 * (WEEKS_PER_YEAR * 2.5).ceil() as u64); + + assert_eq!(Utc::today().and_hms_opt(0, 0, 0).unwrap().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::today().and_hms_opt(0, 0, 0).unwrap().years_since(two_year_ago), Some(2)); + + // If the given DateTime is later than now, the function will always return 0. + let future = + Utc::today().and_hms_opt(0, 0, 0).unwrap() + Duration::from_secs(7 * 24 * 60 * 60 * 12); + assert_eq!(Utc::today().and_hms_opt(0, 0, 0).unwrap().years_since(future), None); +} + #[test] fn test_datetime_add_assign() { let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let datetime = DateTime::::from_utc(naivedatetime, Utc); let mut datetime_add = datetime; - datetime_add += Duration::seconds(60); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + datetime_add += OldDuration::seconds(60); + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + OldDuration::seconds(60)); +} + +#[test] +fn test_datetime_add_assign_duration() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_add = datetime; + + datetime_add += Duration::from_secs(60); + assert_eq!(datetime_add, datetime + Duration::from_secs(60)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + assert_eq!(datetime_add, datetime + Duration::from_secs(60)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_add = datetime_add.with_timezone(&timezone); - assert_eq!(datetime_add, datetime + Duration::seconds(60)); + assert_eq!(datetime_add, datetime + Duration::from_secs(60)); } #[test] @@ -676,8 +796,23 @@ fn test_datetime_add_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_add += Duration::days(1); - assert_eq!(datetime_add, datetime + Duration::days(i)) + datetime_add += OldDuration::days(1); + assert_eq!(datetime_add, datetime + OldDuration::days(i)) + } +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_add_assign_local_duration() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_add = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_add += Duration::from_secs(24 * 60 * 60); + assert_eq!(datetime_add, datetime + Duration::from_secs(24 * 60 * 60 * i)) } } @@ -687,20 +822,42 @@ fn test_datetime_sub_assign() { let datetime = DateTime::::from_utc(naivedatetime, Utc); let mut datetime_sub = datetime; - datetime_sub -= Duration::minutes(90); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + datetime_sub -= OldDuration::minutes(90); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); let timezone = FixedOffset::east_opt(60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); let datetime = datetime.with_timezone(&timezone); let datetime_sub = datetime_sub.with_timezone(&timezone); - assert_eq!(datetime_sub, datetime - Duration::minutes(90)); + assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); +} + +#[test] +fn test_datetime_sub_assign_duration() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(); + let datetime = DateTime::::from_utc(naivedatetime, Utc); + let mut datetime_sub = datetime; + + datetime_sub -= Duration::from_secs(60 * 90); + assert_eq!(datetime_sub, datetime - Duration::from_secs(60 * 90)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::from_secs(60 * 90)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - Duration::from_secs(60 * 90)); } #[test] @@ -713,7 +870,22 @@ fn test_datetime_sub_assign_local() { // ensure we cross a DST transition for i in 1..=365 { - datetime_sub -= Duration::days(1); - assert_eq!(datetime_sub, datetime - Duration::days(i)) + datetime_sub -= OldDuration::days(1); + assert_eq!(datetime_sub, datetime - OldDuration::days(i)) + } +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_sub_assign_local_duration() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_sub -= Duration::from_secs(24 * 60 * 60); + assert_eq!(datetime_sub, datetime - Duration::from_secs(24 * 60 * 60 * i)) } } diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 54679ccda1..212eb2452e 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -3,6 +3,8 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. +use core::convert::TryFrom; +use core::time::Duration; use num_integer::div_rem; use num_traits::ToPrimitive; @@ -10,10 +12,9 @@ use num_traits::ToPrimitive; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Weekday; -use crate::{Datelike, Timelike}; +use crate::{Datelike, Days, Timelike}; /// Parsed parts of date and time. There are two classes of methods: /// @@ -424,12 +425,18 @@ impl Parsed { if week_from_sun > 53 { return Err(OUT_OF_RANGE); } // can it overflow? - let ndays = firstweek - + (week_from_sun as i32 - 1) * 7 - + weekday.num_days_from_sunday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; + + let ndays = if week_from_sun == 0 { + if firstweek + weekday.num_days_from_sunday() < 7 { + return Err(OUT_OF_RANGE); + } + firstweek + weekday.num_days_from_sunday() - 7 + } else { + firstweek + (week_from_sun - 1) * 7 + weekday.num_days_from_sunday() + }; + + let date = newyear.checked_add_days(Days::new(ndays.into())).ok_or(OUT_OF_RANGE)?; + if date.year() != year { return Err(OUT_OF_RANGE); } // early exit for correct error @@ -458,12 +465,18 @@ impl Parsed { if week_from_mon > 53 { return Err(OUT_OF_RANGE); } // can it overflow? - let ndays = firstweek - + (week_from_mon as i32 - 1) * 7 - + weekday.num_days_from_monday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; + + let ndays = if week_from_mon == 0 { + if firstweek + weekday.num_days_from_monday() < 7 { + return Err(OUT_OF_RANGE); + } + firstweek + weekday.num_days_from_monday() - 7 + } else { + firstweek + (week_from_mon - 1) * 7 + weekday.num_days_from_monday() + }; + + let date = newyear.checked_add_days(Days::new(ndays.into())).ok_or(OUT_OF_RANGE)?; + if date.year() != year { return Err(OUT_OF_RANGE); } // early exit for correct error @@ -585,7 +598,7 @@ impl Parsed { 59 => {} // `datetime` is known to be off by one second. 0 => { - datetime -= OldDuration::seconds(1); + datetime -= Duration::from_secs(1); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), @@ -628,9 +641,17 @@ impl Parsed { let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; // this is used to prevent an overflow when calling FixedOffset::from_local_datetime - datetime - .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) - .ok_or(OUT_OF_RANGE)?; + if offset.local_minus_utc() < 0 { + datetime + .checked_add(Duration::from_secs( + u64::try_from(offset.local_minus_utc().abs()).unwrap(), + )) + .ok_or(OUT_OF_RANGE)?; + } else { + datetime + .checked_sub(Duration::from_secs(u64::try_from(offset.local_minus_utc()).unwrap())) + .ok_or(OUT_OF_RANGE)?; + } match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), @@ -1084,22 +1105,22 @@ mod tests { // more timestamps let max_days_from_year_1970 = - NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + NaiveDate::MAX.days_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) .unwrap() - .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + .days_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let min_days_from_year_1970 = - NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + NaiveDate::MIN.days_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); assert_eq!( - parse!(timestamp: min_days_from_year_1970.num_seconds()), + parse!(timestamp: min_days_from_year_1970 * 24 * 60 * 60), ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( - parse!(timestamp: year_0_from_year_1970.num_seconds()), + parse!(timestamp: year_0_from_year_1970 * 24 * 60 * 60), ymdhms(0, 1, 1, 0, 0, 0) ); assert_eq!( - parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), + parse!(timestamp: (max_days_from_year_1970 * 24 * 60 * 60) + 86399), ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); diff --git a/src/lib.rs b/src/lib.rs index 861ee10593..adabda7058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,9 +48,8 @@ //! //! ### Duration //! -//! Chrono currently uses its own [`Duration`] type to represent the magnitude -//! of a time span. Since this has the same name as the newer, standard type for -//! duration, the reference will refer this type as `OldDuration`. +//! Chrono currently uses the core library's [`core::time::Duration`] type to represent the magnitude +//! of a time span. //! //! Note that this is an "accurate" duration represented as seconds and //! nanoseconds and does not represent "nominal" components such as days or @@ -157,8 +156,8 @@ //! The following illustrates most supported operations to the date and time: //! //! ```rust +//! use core::time::Duration; //! use chrono::prelude::*; -//! use chrono::Duration; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: //! let dt = FixedOffset::east_opt(9*3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_nano_opt(21, 45, 59, 324310806).unwrap()).unwrap(); @@ -183,13 +182,14 @@ //! assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE //! //! // arithmetic operations + //! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); //! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); -//! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -//! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + Duration::seconds(1_000_000_000), +//! assert_eq!(dt1.checked_duration_since(dt2), Err(Duration::from_secs(2 * 3600 - 2))); +//! assert_eq!(dt2.checked_duration_since(dt1), Ok(Duration::from_secs(2 * 3600 - 2))); +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + Duration::from_secs(1_000_000_000), //! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()); -//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - Duration::seconds(1_000_000_000), +//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - Duration::from_secs(1_000_000_000), //! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()); //! ``` //! @@ -416,7 +416,9 @@ extern crate time as oldtime; #[cfg(not(feature = "oldtime"))] mod oldtime; // this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::{Duration, OutOfRangeError}; +#[deprecated(since = "0.4.24", note = "Use core::time::Duration instead")] +pub use oldtime::Duration; +pub use oldtime::OutOfRangeError; #[cfg(feature = "__doctest")] #[cfg_attr(feature = "__doctest", cfg(doctest))] @@ -428,6 +430,8 @@ doctest!("../README.md"); /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { + #[doc(no_inline)] + pub use crate::CoreSubsecRound; #[doc(no_inline)] #[allow(deprecated)] pub use crate::Date; @@ -440,6 +444,7 @@ pub mod prelude { #[doc(no_inline)] pub use crate::Locale; #[doc(no_inline)] + #[allow(deprecated)] pub use crate::SubsecRound; #[doc(no_inline)] pub use crate::{DateTime, SecondsFormat}; @@ -484,7 +489,9 @@ pub use offset::Local; pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; mod round; -pub use round::{DurationRound, RoundingError, SubsecRound}; +#[allow(deprecated)] +pub use round::SubsecRound; +pub use round::{CoreSubsecRound, DurationRound, RoundingError}; mod weekday; pub use weekday::{ParseWeekdayError, Weekday}; diff --git a/src/naive/date.rs b/src/naive/date.rs index 9db2ccf9a9..3bb0d1328f 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -6,11 +6,12 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::convert::TryFrom; +use core::hash::Hash; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; use num_integer::div_mod_floor; -use num_traits::ToPrimitive; + #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -21,7 +22,7 @@ use crate::format::{Item, Numeric, Pad}; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Duration, Weekday}; +use crate::{Datelike, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -77,7 +78,7 @@ impl NaiveWeek { let start = self.start.num_days_from_monday(); let end = self.date.weekday().num_days_from_monday(); let days = if start > end { 7 - start + end } else { end - start }; - self.date - Duration::days(days.into()) + self.date - Days::new(days.into()) } /// Returns a date representing the last day of the week. @@ -93,7 +94,7 @@ impl NaiveWeek { /// ``` #[inline] pub fn last_day(&self) -> NaiveDate { - self.first_day() + Duration::days(6) + self.first_day() + Days::new(6) } /// Returns a [`RangeInclusive`] representing the whole week bounded by @@ -119,10 +120,9 @@ impl NaiveWeek { /// A duration in calendar days. /// /// This is useful becuase when using `Duration` it is possible -/// that adding `Duration::days(1)` doesn't increment the day value as expected due to it being a -/// fixed number of seconds. This difference applies only when dealing with `DateTime` data types -/// and in other cases `Duration::days(n)` and `Days::new(n)` are equivalent. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +/// that adding `Duration::from_secs(24 * 60 * 60)` doesn't increment the day value as expected due to it being a +/// fixed number of seconds. This difference applies only when dealing with `DateTime` data types. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Days(pub(crate) u64); impl Days { @@ -130,6 +130,10 @@ impl Days { pub fn new(num: u64) -> Self { Self(num) } + + pub(crate) fn duration(self) -> core::time::Duration { + core::time::Duration::new(86400 * self.0, 0) + } } /// ISO 8601 calendar date without timezone. @@ -648,7 +652,19 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.diff_days(d)) + let year = self.year(); + let (mut year_div_400, year_mod_400) = div_mod_floor(i64::from(year), 400); + let cycle = internals::yo_to_cycle(u32::try_from(year_mod_400).ok()?, self.of().ordinal()); + let cycle = cycle.checked_add(u32::try_from(days.0).ok()?)?; + let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); + + year_div_400 += i64::from(cycle_div_400y); + + let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle); + let flags = YearFlags::from_year_mod_400(i32::try_from(year_mod_400).ok()?); + + let year = i32::try_from(year_div_400 * 400 + i64::from(year_mod_400)).ok()?; + NaiveDate::from_of(year, Of::new(ordinal, flags)?) } /// Subtract a duration in [`Days`] from the date @@ -667,11 +683,24 @@ impl NaiveDate { return Some(self); } - i64::try_from(days.0).ok().and_then(|d| self.diff_days(-d)) - } + let year = self.year(); + let (mut year_div_400, year_mod_400) = div_mod_floor(i64::from(year), 400); + let cycle = i64::from(internals::yo_to_cycle( + u32::try_from(year_mod_400).unwrap(), + self.of().ordinal(), + )); + let cycle = cycle.checked_sub(i64::try_from(days.0).ok()?)?; + let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); + + year_div_400 += cycle_div_400y; + + let (year_mod_400, ordinal) = internals::cycle_to_yo(u32::try_from(cycle).ok()?); + let flags = YearFlags::from_year_mod_400(i32::try_from(year_mod_400).ok()?); - fn diff_days(self, days: i64) -> Option { - self.checked_add_signed(Duration::days(days)) + NaiveDate::from_of( + i32::try_from(year_div_400 * 400 + i64::from(year_mod_400)).ok()?, + Of::new(ordinal, flags)?, + ) } /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. @@ -977,16 +1006,10 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MAX.checked_add_signed(Duration::days(1)), None); /// ``` pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_add(rhs.num_days().to_i32()?)?; - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) + match rhs.num_days() < 0 { + true => self.checked_sub_days(Days::new(u64::try_from(rhs.num_days().abs()).ok()?)), + false => self.checked_add_days(Days::new(u64::try_from(rhs.num_days()).ok()?)), + } } /// Subtracts the `days` part of given `Duration` from the current date. @@ -1008,16 +1031,23 @@ impl NaiveDate { /// assert_eq!(NaiveDate::MIN.checked_sub_signed(Duration::days(1)), None); /// ``` pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = (cycle as i32).checked_sub(rhs.num_days().to_i32()?)?; - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; + match rhs.num_days() < 0 { + true => self.checked_add_days(Days::new(u64::try_from(rhs.num_days().abs()).ok()?)), + false => self.checked_sub_days(Days::new(u64::try_from(rhs.num_days()).ok()?)), + } + } - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?) + /// Subtracts another `NaiveDate` from the current date. + /// Returns a `DaysDelta` of the number of days between the two dates. + pub fn days_since(self, rhs: NaiveDate) -> i64 { + let year1 = self.year(); + let year2 = rhs.year(); + let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); + let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); + let cycle1 = i64::from(internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal())); + let cycle2 = i64::from(internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal())); + + (i64::from(year1_div_400) - i64::from(year2_div_400)) * 146_097 + (cycle1 - cycle2) } /// Subtracts another `NaiveDate` from the current date. @@ -1043,15 +1073,7 @@ impl NaiveDate { /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); /// ``` pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { - let year1 = self.year(); - let year2 = rhs.year(); - let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); - let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = i64::from(internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal())); - let cycle2 = i64::from(internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal())); - OldDuration::days( - (i64::from(year1_div_400) - i64::from(year2_div_400)) * 146_097 + (cycle1 - cycle2), - ) + OldDuration::days(self.days_since(rhs)) } /// Returns the number of whole years from the given `base` until `self`. @@ -1660,6 +1682,12 @@ impl Add for NaiveDate { } } +impl AddAssign for NaiveDate { + fn add_assign(&mut self, days: Days) { + *self = self.checked_add_days(days).unwrap(); + } +} + impl Sub for NaiveDate { type Output = NaiveDate; @@ -1668,6 +1696,12 @@ impl Sub for NaiveDate { } } +impl SubAssign for NaiveDate { + fn sub_assign(&mut self, days: Days) { + *self = self.checked_sub_days(days).unwrap(); + } +} + /// A subtraction of `Duration` from `NaiveDate` discards the fractional days, /// rounding to the closest integral number of days towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. @@ -1761,7 +1795,7 @@ impl Iterator for NaiveDateDaysIterator { } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_days(); + let exact_size = usize::try_from(NaiveDate::MAX.days_since(self.value)).unwrap(); (exact_size as usize, Some(exact_size as usize)) } } @@ -1788,16 +1822,16 @@ impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if NaiveDate::MAX - self.value < OldDuration::weeks(1) { + if NaiveDate::MAX.days_since(self.value) < 7 { return None; } let current = self.value; - self.value = current + OldDuration::weeks(1); + self.value = current + Days::new(7); Some(current) } fn size_hint(&self) -> (usize, Option) { - let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_weeks(); + let exact_size = usize::try_from(NaiveDate::MAX.days_since(self.value)).unwrap(); (exact_size as usize, Some(exact_size as usize)) } } @@ -1806,11 +1840,11 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - NaiveDate::MIN < OldDuration::weeks(1) { + if self.value.days_since(NaiveDate::MIN) < 7 { return None; } let current = self.value; - self.value = current - OldDuration::weeks(1); + self.value = current - Days::new(7); Some(current) } } @@ -2675,6 +2709,26 @@ mod tests { check((0, 1, 1), (MIN_YEAR, 1, 1), Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap())); } + #[test] + fn test_days_addassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 1); + date += Days::new(10); + assert_eq!(date, ymd(2016, 10, 11)); + date += Days::new(30); + assert_eq!(date, ymd(2016, 11, 10)); + } + + #[test] + fn test_days_subassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 11); + date -= Days::new(10); + assert_eq!(date, ymd(2016, 10, 1)); + date -= Days::new(2); + assert_eq!(date, ymd(2016, 9, 29)); + } + #[test] fn test_date_addassignment() { let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 5cf29739fa..af1a74037b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -5,9 +5,11 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; +use core::cmp::Ordering; use core::convert::TryFrom; use core::fmt::Write; -use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::ops::{Add, AddAssign, Neg, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, str}; use num_integer::div_mod_floor; @@ -555,15 +557,101 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_add_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); /// ``` + #[deprecated(since = "0.4.24", note = "Use checked_add() instead")] pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_add_signed(rhs); + let (date, time) = match rhs < OldDuration::zero() { + false => { + let (time, days) = self.time.overflowing_add_opt(rhs.to_std().ok()?)?; + if days.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { + return None; + } + let date = self.date.checked_add_days(days)?; + (date, time) + } + true => { + let (time, days) = self.time.overflowing_sub_opt(rhs.neg().to_std().ok()?)?; + if days.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { + return None; + } + let date = self.date.checked_sub_days(days)?; + (date, time) + } + }; - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { + Some(NaiveDateTime { date, time }) + } + + /// Adds given `Duration` to the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the addition assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// Returns `None` when it will result in overflow. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// use core::time::Duration; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::from_secs(0)), + /// Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::from_secs(1)), + /// Some(hms(3, 5, 8))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::from_secs(3600 + 60)), + /// Some(hms(4, 6, 7))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::from_secs(86_400)), + /// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7))); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// assert_eq!(hmsm(3, 5, 7, 980).checked_add(Duration::from_millis(450)), + /// Some(hmsm(3, 5, 8, 430))); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::from_secs(24 * 60 * 60 * 1_000_000_000)), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the addition assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// # let from_ymd = NaiveDate::from_ymd; + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_add(Duration::from_secs(0)), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_add(Duration::from_millis(500)), + /// Some(hmsm(3, 5, 59, 1_800))); + /// assert_eq!(leap.checked_add(Duration::from_millis(800)), + /// Some(hmsm(3, 6, 0, 100))); + /// assert_eq!(leap.checked_add(Duration::from_secs(10)), + /// Some(hmsm(3, 6, 9, 300))); + /// assert_eq!(leap.checked_add(Duration::from_secs(24 * 60 * 60)), + /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); + /// ``` + pub fn checked_add(self, rhs: Duration) -> Option { + let (time, rhs) = self.time.overflowing_add_opt(rhs)?; + + // early checking to avoid overflow in OldTimeDelta::seconds + if rhs.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { return None; } - let date = self.date.checked_add_signed(OldDuration::seconds(rhs))?; + let date = self.date.checked_add_days(rhs)?; Some(NaiveDateTime { date, time }) } @@ -595,6 +683,80 @@ impl NaiveDateTime { Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) } + /// Subtracts given `Duration` from the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// Returns `None` when it will result in overflow. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// use core::time::Duration; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::from_secs(0)), + /// Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::from_secs(1)), + /// Some(hms(3, 5, 6))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::from_secs(3600 + 60)), + /// Some(hms(2, 4, 7))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::from_secs(86_400)), + /// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7))); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub(Duration::from_millis(670)), + /// Some(hmsm(3, 5, 6, 780))); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::from_secs(24 * 60 * 60 * 1_000_000_000)), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the subtraction assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// # let from_ymd = NaiveDate::from_ymd; + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_sub(Duration::from_secs(0)), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_sub(Duration::from_millis(200)), + /// Some(hmsm(3, 5, 59, 1_100))); + /// assert_eq!(leap.checked_sub(Duration::from_millis(500)), + /// Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_sub(Duration::from_secs(60)), + /// Some(hmsm(3, 5, 0, 300))); + /// assert_eq!(leap.checked_sub(Duration::from_secs(24 * 60 * 60)), + /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); + /// ``` + pub fn checked_sub(self, rhs: Duration) -> Option { + let (time, rhs) = self.time.overflowing_sub_opt(rhs)?; + + // early checking to avoid overflow in OldTimeDelta::seconds + if rhs.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { + return None; + } + + let date = self.date.checked_sub_days(rhs)?; + Some(NaiveDateTime { date, time }) + } + /// Subtracts given `Duration` from the current date and time. /// /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), @@ -656,15 +818,27 @@ impl NaiveDateTime { /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); /// ``` + #[deprecated(since = "0.4.24", note = "Use checked_sub() instead")] pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_sub_signed(rhs); - - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } + let (date, time) = match rhs < OldDuration::zero() { + true => { + let (time, days) = self.time.overflowing_add_opt(rhs.neg().to_std().ok()?)?; + if days.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { + return None; + } + let date = self.date.checked_add_days(days)?; + (date, time) + } + false => { + let (time, days) = self.time.overflowing_sub_opt(rhs.to_std().ok()?)?; + if days.0 * 24 * 60 * 60 >= (1 << MAX_SECS_BITS) { + return None; + } + let date = self.date.checked_sub_days(days)?; + (date, time) + } + }; - let date = self.date.checked_sub_signed(OldDuration::seconds(rhs))?; Some(NaiveDateTime { date, time }) } @@ -748,8 +922,131 @@ impl NaiveDateTime { /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` + #[deprecated(since = "0.4.24", note = "Use checked_duration_since() or abs_diff() instead")] pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { - self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) + let days = OldDuration::days(self.date.days_since(rhs.date)); + match self.time.cmp(&rhs.time) { + Ordering::Less => { + days - OldDuration::from_std(self.time.abs_duration_since(rhs.time)) + .expect("Should succeed") + } + Ordering::Equal => days, + Ordering::Greater => { + days + OldDuration::from_std(self.time.abs_duration_since(rhs.time)) + .expect("Should succeed") + } + } + } + + /// Subtracts another `NaiveDateTime` from the current date and time. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveDateTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// use core::time::Duration; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// assert_eq!(d.and_hms(3, 5, 7).abs_diff(d.and_hms(2, 4, 6)), + /// Duration::from_secs(3600 + 60 + 1)); + /// + /// // July 8 is 190th day in the year 2016 + /// let d0 = from_ymd(2016, 1, 1); + /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).abs_diff(d0.and_hms(0, 0, 0)), + /// Duration::from_secs(189 * 86_400 + 7 * 60 + 6) + Duration::from_millis(500)); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// let leap = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); + /// assert_eq!(leap.abs_diff(NaiveDate::from_ymd(2015, 6, 30).and_hms(23, 0, 0)), + /// Duration::from_secs(3600) + Duration::from_millis(500)); + /// assert_eq!(NaiveDate::from_ymd(2015, 7, 1).and_hms(1, 0, 0).abs_diff(leap), + /// Duration::from_secs(3600) - Duration::from_millis(500)); + /// ``` + pub fn abs_diff(self, rhs: NaiveDateTime) -> Duration { + let date_diff = self.date.days_since(rhs.date); + let time_diff = self.time.abs_duration_since(rhs.time); + match self.cmp(&rhs) { + Ordering::Equal | Ordering::Greater => { + // date_diff must be >= 0 + let base = Days::new(u64::try_from(date_diff).unwrap()).duration(); + match self.time.cmp(&rhs.time) { + Ordering::Less => base - time_diff, + Ordering::Equal => base, + Ordering::Greater => base + time_diff, + } + } + Ordering::Less => { + // date_diff must be <= 0 + let base = Days::new(u64::try_from(date_diff.abs()).unwrap()).duration(); + match self.time.cmp(&rhs.time) { + Ordering::Less => base + time_diff, + Ordering::Equal => base, + Ordering::Greater => base - time_diff, + } + } + } + } + + /// Subtracts another `NaiveDateTime` from the current date and time. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveDateTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// use core::time::Duration; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// assert_eq!(d.and_hms(3, 5, 7).checked_duration_since(d.and_hms(2, 4, 6)), + /// Ok(Duration::from_secs(3600 + 60 + 1))); + /// + /// // July 8 is 190th day in the year 2016 + /// let d0 = from_ymd(2016, 1, 1); + /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).checked_duration_since(d0.and_hms(0, 0, 0)), + /// Ok(Duration::from_secs(189 * 86_400 + 7 * 60 + 6) + Duration::from_millis(500))); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use core::time::Duration; + /// let leap = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); + /// assert_eq!(leap.checked_duration_since(NaiveDate::from_ymd(2015, 6, 30).and_hms(23, 0, 0)), + /// Ok(Duration::from_secs(3600) + Duration::from_millis(500))); + /// assert_eq!(NaiveDate::from_ymd(2015, 7, 1).and_hms(1, 0, 0).checked_duration_since(leap), + /// Ok(Duration::from_secs(3600) - Duration::from_millis(500))); + /// ``` + pub fn checked_duration_since(self, rhs: NaiveDateTime) -> Result { + let diff = self.abs_diff(rhs); + match self >= rhs { + true => Ok(diff), + false => Err(diff), + } } /// Formats the combined date and time with the specified formatting items. @@ -1379,6 +1676,8 @@ impl Timelike for NaiveDateTime { /// assert_eq!(leap + Duration::days(1), /// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); /// ``` +#[allow(deprecated)] + impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1388,6 +1687,7 @@ impl Add for NaiveDateTime { } } +#[allow(deprecated)] impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1395,6 +1695,70 @@ impl AddAssign for NaiveDateTime { } } +/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveDateTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_add`] +/// to detect that. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// use core::time::Duration; +/// +/// let from_ymd = NaiveDate::from_ymd; +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms(h, m, s); +/// assert_eq!(hms(3, 5, 7) + Duration::from_secs(0), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::from_secs(1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + Duration::from_secs(3600 + 60), hms(4, 6, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::from_secs(86_400), +/// from_ymd(2016, 7, 9).and_hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::from_secs(24 * 60 * 60 * 365), +/// from_ymd(2017, 7, 8).and_hms(3, 5, 7)); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// assert_eq!(hmsm(3, 5, 7, 980) + Duration::from_millis(450), hmsm(3, 5, 8, 430)); +/// ``` +/// +/// Leap seconds are handled, +/// but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::NaiveDate; +/// # use core::time::Duration; +/// # let from_ymd = NaiveDate::from_ymd; +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + Duration::from_secs(0), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + Duration::from_millis(500), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + Duration::from_millis(800), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + Duration::from_secs(10), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + Duration::from_secs(24 * 60 * 60), +/// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); +/// ``` +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveDateTime { + self.checked_add(rhs).expect("`NaiveDateTime + Duration` overflowed") + } +} + +impl AddAssign for NaiveDateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1488,6 +1852,7 @@ impl Add for NaiveDateTime { /// assert_eq!(leap - Duration::days(1), /// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); /// ``` +#[allow(deprecated)] impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1497,6 +1862,7 @@ impl Sub for NaiveDateTime { } } +#[allow(deprecated)] impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1504,6 +1870,71 @@ impl SubAssign for NaiveDateTime { } } +/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. +/// It is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveDateTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_sub`] +/// to detect that. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// use core::time::Duration; +/// +/// let from_ymd = NaiveDate::from_ymd; +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms(h, m, s); +/// assert_eq!(hms(3, 5, 7) - Duration::from_secs(0), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::from_secs(1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - Duration::from_secs(3600 + 60), hms(2, 4, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::from_secs(86_400), +/// from_ymd(2016, 7, 7).and_hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::from_secs(24 * 60 * 60 * 365), +/// from_ymd(2015, 7, 9).and_hms(3, 5, 7)); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// assert_eq!(hmsm(3, 5, 7, 450) - Duration::from_millis(670), hmsm(3, 5, 6, 780)); +/// ``` +/// +/// Leap seconds are handled, +/// but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::NaiveDate; +/// # use core::time::Duration; +/// # let from_ymd = NaiveDate::from_ymd; +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - Duration::from_secs(0), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - Duration::from_millis(200), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - Duration::from_millis(500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - Duration::from_secs(60), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - Duration::from_secs(24 * 60 * 60), +/// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); +/// ``` +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveDateTime { + self.checked_sub(rhs).expect("`NaiveDateTime - Duration` overflowed") + } +} + +impl SubAssign for NaiveDateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs); + } +} + /// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. /// /// # Panics @@ -1576,6 +2007,7 @@ impl Sub for NaiveDateTime { /// assert_eq!(from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, /// Duration::seconds(3600) - Duration::milliseconds(500)); /// ``` +#[allow(deprecated)] impl Sub for NaiveDateTime { type Output = OldDuration; diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 01c21f1004..f6ad50b13d 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,8 +1,11 @@ -use super::NaiveDateTime; -use crate::oldtime::Duration; -use crate::NaiveDate; -use crate::{Datelike, FixedOffset, Utc}; +use std::convert::TryFrom; use std::i64; +use std::time::Duration; + +use super::NaiveDateTime; +use crate::naive::NaiveDate; +use crate::oldtime::Duration as OldDuration; +use crate::{Datelike, Days, FixedOffset, Utc}; #[test] fn test_datetime_from_timestamp() { @@ -19,10 +22,11 @@ fn test_datetime_from_timestamp() { } #[test] +#[allow(deprecated)] fn test_datetime_add() { fn check( (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: Duration, + rhs: OldDuration, result: Option<(i32, u32, u32, u32, u32, u32)>, ) { let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); @@ -33,55 +37,197 @@ fn test_datetime_add() { assert_eq!(lhs.checked_sub_signed(-rhs), sum); } - check((2014, 5, 6, 7, 8, 9), Duration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check( + (2014, 5, 6, 7, 8, 9), + OldDuration::seconds(-(3600 + 60 + 1)), + Some((2014, 5, 6, 6, 7, 8)), + ); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), OldDuration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); // overflow check // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = - NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); + let max_days_from_year_0 = Days::new( + u64::try_from( + NaiveDate::MAX.num_days_from_ce() + - NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), + ) + .unwrap(), + ) + .duration(); + + let max_days_from_year_0 = OldDuration::from_std(max_days_from_year_0).unwrap(); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); check( (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + Duration::seconds(86399), + max_days_from_year_0 + OldDuration::seconds(86399), Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + OldDuration::seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), OldDuration::max_value(), None); +} + +#[test] +fn test_datetime_add_duration() { + fn check( + (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), + rhs: Duration, + result: Option<(i32, u32, u32, u32, u32, u32)>, + ) { + let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let sum = result.map(|(y, m, d, h, n, s)| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }); + assert_eq!(lhs.checked_add(rhs), sum); + } + + check((2014, 5, 6, 7, 8, 9), Duration::from_secs(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), Duration::from_secs(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), Duration::from_secs(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), Duration::from_secs(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - let min_days_from_year_0 = - NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); + // overflow check + // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. + // (they are private constants, but the equivalence is tested in that module.) + let max_days_from_year_0 = Days::new( + u64::try_from( + NaiveDate::MAX.num_days_from_ce() + - NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), + ) + .unwrap(), + ) + .duration(); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); + check( + (0, 1, 1, 0, 0, 0), + max_days_from_year_0 + Duration::from_secs(86399), + Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), + ); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::from_secs(86_400), None); + check((0, 1, 1, 0, 0, 0), Duration::new(core::u64::MAX, 999_999_999), None); } #[test] +#[allow(deprecated)] fn test_datetime_sub() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let since = NaiveDateTime::signed_duration_since; - assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), Duration::zero()); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), + OldDuration::zero() + ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::seconds(1) + OldDuration::seconds(1) ); assert_eq!( since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(-1) + OldDuration::seconds(-1) ); assert_eq!( since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(86399) + OldDuration::seconds(86399) ); assert_eq!( since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - Duration::seconds(999_999_999) + OldDuration::seconds(999_999_999) + ); + + let min_days_from_year_0 = Days::new( + u64::try_from( + NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce() + - NaiveDate::MIN.num_days_from_ce(), + ) + .unwrap(), + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0).checked_sub_days(min_days_from_year_0), + Some(ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0)) + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0).checked_sub_signed( + OldDuration::from_std(min_days_from_year_0.duration() + Duration::from_secs(1)) + .unwrap() + ), + None + ); +} + +#[test] +fn test_datetime_sub_duration() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let since = NaiveDateTime::checked_duration_since; + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), + Ok(Duration::from_secs(0)) + ); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), + Ok(Duration::from_secs(1)) + ); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + Err(Duration::from_secs(1)) + ); + assert_eq!( + since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + Ok(Duration::from_secs(86399)) + ); + assert_eq!( + since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), + Ok(Duration::from_secs(999_999_999)) + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0).checked_sub(Duration::new(core::u64::MAX, 999_999_999)), + None + ); + + let min_days_from_year_0 = Days::new( + u64::try_from( + NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce() + - NaiveDate::MIN.num_days_from_ce(), + ) + .unwrap(), + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0).checked_sub_days(min_days_from_year_0), + Some(ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0)) + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0) + .checked_sub(min_days_from_year_0.duration() + Duration::from_secs(1)), + None + ); + + let min_days_from_year_0 = Days::new( + u64::try_from( + NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce() + - NaiveDate::MIN.num_days_from_ce(), + ) + .unwrap(), + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0).checked_sub_days(min_days_from_year_0), + Some(ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0)) + ); + + assert_eq!( + ymdhms(0, 1, 1, 0, 0, 0) + .checked_sub(min_days_from_year_0.duration() + Duration::from_secs(1)), + None ); } @@ -90,9 +236,9 @@ fn test_datetime_addassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += Duration::minutes(10_000_000); + date += OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += Duration::days(10); + date += OldDuration::days(10); assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); } @@ -101,9 +247,31 @@ fn test_datetime_subassignment() { let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= Duration::minutes(10_000_000); + date -= OldDuration::minutes(10_000_000); assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= Duration::days(10); + date -= OldDuration::days(10); + assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); +} + +#[test] +fn test_datetime_addassignment_duration() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date += Duration::from_secs(60 * 10_000_000); + assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); + date += Duration::from_secs(24 * 60 * 60 * 10); + assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); +} + +#[test] +fn test_datetime_subassignment_duration() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date -= Duration::from_secs(60 * 10_000_000); + assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); + date -= Duration::from_secs(24 * 60 * 60 * 10); assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); } @@ -227,14 +395,24 @@ fn test_datetime_format() { } #[test] +#[allow(deprecated)] fn test_datetime_add_sub_invariant() { // issue #37 let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let t = -946684799990000; - let time = base + Duration::microseconds(t); + let time = base + OldDuration::microseconds(t); assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); } +#[test] +fn test_datetime_add_sub_invariant_duration() { + // issue #37 + let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let t = 946684799990000; + let time = base - Duration::from_micros(t); + assert_eq!(u128::from(t), time.checked_duration_since(base).err().unwrap().as_micros()); +} + #[test] fn test_nanosecond_range() { const A_BILLION: i64 = 1_000_000_000; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 260423a9d1..3f1f77207d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -5,7 +5,9 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; +use core::convert::TryFrom; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; use core::{fmt, str}; use num_integer::div_mod_floor; @@ -17,7 +19,8 @@ use crate::format::DelayedFormat; use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::oldtime::Duration as OldDuration; -use crate::Timelike; +use crate::{Days, Timelike}; +use core::cmp::Ordering; #[cfg(feature = "rustc-serialize")] mod rustc_serialize; @@ -28,6 +31,8 @@ mod serde; #[cfg(test)] mod tests; +const SECOND_AS_NANOS: u32 = 1_000_000_000; + /// ISO 8601 time without timezone. /// Allows for the nanosecond precision and optional leap second representation. /// @@ -473,6 +478,163 @@ impl NaiveTime { parsed.to_naive_time() } + /// Adds given `Duration` to the current time + /// + /// This returns the new target time (which may have overflowed to a new day) + /// and also returns the number of *seconds* in the integral number of days + /// which were ignored from the addition. + /// + /// # Example + /// + /// ``` + /// use chrono::{Days, NaiveTime}; + /// use core::time::Duration; + /// + /// let from_hms = NaiveTime::from_hms; + /// + /// assert_eq!(from_hms(3, 4, 5).overflowing_add_opt(Duration::from_secs(60 * 60 * 11)), + /// Some((from_hms(14, 4, 5), Days::new(0)))); + /// assert_eq!(from_hms(3, 4, 5).overflowing_add_opt(Duration::from_secs(60 * 60 * 23)), + /// Some((from_hms(2, 4, 5), Days::new(1)))); + /// ``` + pub fn overflowing_add_opt(&self, rhs: Duration) -> Option<(NaiveTime, Days)> { + let mut frac = self.frac; + + // check if `self` is a leap second and adding `rhs` would escape that leap second. + // if it's the case, update `self` and `rhs` to involve no leap second; + // otherwise the addition immediately finishes. + if frac >= SECOND_AS_NANOS { + if rhs.as_nanos() + u128::from(frac) < u128::from(2 * SECOND_AS_NANOS) { + // stays within the leap second + // shouldn't overflow as frac is >= 1_000_000_000 + frac += rhs.subsec_nanos(); + debug_assert!(frac < 2_000_000_000); + return Some((NaiveTime { secs: self.secs, frac }, Days::new(0))); + } else { + // leaves the leap second + // so normalise the NaiveTime to not be in a leap second + // but we also loose a whole second + // as the leap second consumes it + + frac -= SECOND_AS_NANOS; + } + } + + debug_assert!(frac < 1_000_000_000); + + let rhssecs = rhs.as_secs(); + let rhsfrac = rhs.subsec_nanos(); + + // if the sum of the two nanos fractions is larger than a second, + // then we will increase on more second and use the remaining fraction for nanos + let (total_seconds, remaining_nanos) = if frac + rhsfrac >= SECOND_AS_NANOS { + (u64::from(self.secs) + rhssecs + 1, frac + rhsfrac - SECOND_AS_NANOS) + } else { + (u64::from(self.secs) + rhssecs, frac + rhsfrac) + }; + + let total_days = total_seconds / 86_400; + let remaining_seconds = total_seconds % 86_400; + debug_assert!(remaining_seconds < 86_400); + debug_assert!(remaining_nanos < 1_000_000_000); + + Some(( + NaiveTime { secs: u32::try_from(remaining_seconds).ok()?, frac: remaining_nanos }, + Days::new(total_days), + )) + } + + /// Subtracts given `Duration` to the current time + /// + /// This returns the new target time (which may have overflowed to a new day) + /// and also returns the number of *seconds* in the integral number of days + /// which were ignored from the subtraction. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Days}; + /// use core::time::Duration; + /// + /// let from_hms = NaiveTime::from_hms; + /// + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_opt(Duration::from_secs(60 * 60 * 2)), + /// Some((from_hms(1, 4, 5), Days::new(0)))); + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_opt(Duration::from_secs(60 * 60 * 17)), + /// Some((from_hms(10, 4, 5), Days::new(1)))); + /// ``` + #[inline] + pub fn overflowing_sub_opt(&self, rhs: Duration) -> Option<(NaiveTime, Days)> { + let mut secs = u64::from(self.secs); + let mut frac = self.frac; + + // check if `self` is a leap second and adding `rhs` would escape that leap second. + // if it's the case, update `self` and `rhs` to involve no leap second; + // otherwise the addition immediately finishes. + if frac >= SECOND_AS_NANOS { + if rhs.as_nanos() <= u128::from(frac - SECOND_AS_NANOS) { + // stays within the leap second + frac -= rhs.subsec_nanos(); + debug_assert!(frac < 2_000_000_000); + return Some((NaiveTime { secs: self.secs, frac }, Days::new(0))); + } else { + // leaves the leap second + // so normalise the NaiveTime to not be in a leap second + // here we have to increment the second by one so that once we subtract we + // end up in the right place. + // shouldn't overflow as frac is >= 1_000_000_000 + secs += 1; + frac -= SECOND_AS_NANOS; + } + } + + debug_assert!(secs <= 86_400); + debug_assert!(frac < 1_000_000_000); + + let rhssecs = rhs.as_secs(); + let rhsfrac = rhs.subsec_nanos(); + + // if we want to subtract a larger nanos fraction than the input time has + // then we will move back to the previous second + let (extra_backwards_seconds, remaining_nanos) = if rhsfrac > frac { + (1, frac + SECOND_AS_NANOS - rhsfrac) + } else { + (0, frac - rhsfrac) + }; + + // this can overflow if rhs is Duration::MAX hence we use checked_add. + let total_backwards_seconds = rhssecs.checked_add(extra_backwards_seconds)?; + + if total_backwards_seconds > secs { + // goes backwards a number of days + let backwards_secs_from_0 = total_backwards_seconds - secs; + // we know we will go back at least one day becuase the rhssecs are greater than the secs + let total_days = 1 + backwards_secs_from_0 / 86_400; + + let remaining_backwards_secs = u32::try_from(backwards_secs_from_0 % 86_400).ok()?; + + debug_assert!(remaining_backwards_secs <= 86_400); + + if remaining_backwards_secs == 0 { + Some((NaiveTime { secs: 0, frac: remaining_nanos }, Days::new(total_days - 1))) + } else { + Some(( + NaiveTime { secs: 86_400 - remaining_backwards_secs, frac: remaining_nanos }, + Days::new(total_days), + )) + } + } else { + // stays on same day + Some(( + NaiveTime { + secs: u32::try_from(secs - total_backwards_seconds).ok()?, + frac: remaining_nanos, + }, + Days::new(0), + )) + } + } + /// Adds given `Duration` to the current time, /// and also returns the number of *seconds* /// in the integral number of days ignored from the addition. @@ -645,8 +807,6 @@ impl NaiveTime { // `rhs.frac`|========================================>| // | | | `self - rhs` | | - use core::cmp::Ordering; - let secs = i64::from(self.secs) - i64::from(rhs.secs); let frac = i64::from(self.frac) - i64::from(rhs.frac); @@ -666,6 +826,198 @@ impl NaiveTime { OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) } + /// Find the absolute difference to another `NaiveTime` + /// + /// Returns a `Duration` within +/- 1 day. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// use core::time::Duration; + /// + /// let from_hmsm = NaiveTime::from_hms_milli; + /// let since = NaiveTime::abs_duration_since; + /// + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), + /// Duration::from_secs(0)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), + /// Duration::from_millis(25)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), + /// Duration::from_millis(975)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), + /// Duration::from_secs(7)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), + /// Duration::from_secs(5 * 60)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), + /// Duration::from_secs(3 * 3600)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), + /// Duration::from_secs(3600)); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), + /// Duration::from_secs(3600 + 60 + 1) + Duration::from_millis(100)); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # use core::time::Duration; + /// # let from_hmsm = NaiveTime::from_hms_milli; + /// # let since = NaiveTime::abs_duration_since; + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), + /// Duration::from_secs(1)); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), + /// Duration::from_millis(1500)); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), + /// Duration::from_secs(60)); + /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), + /// Duration::from_secs(1)); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), + /// Duration::from_secs(61)); + /// ``` + pub fn abs_duration_since(self, rhs: NaiveTime) -> Duration { + // | | :leap| | | | | | | :leap| | + // | | : | | | | | | | : | | + // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- + // | `rhs` | | `self` + // |======================================>| | + // | | `self.secs - rhs.secs` |`self.frac` + // |====>| | |======>| + // `rhs.frac`|========================================>| + // | | | `self - rhs` | | + + // importantly, here if the `secs` are larger, then that `NaiveTime` is larger, + // no matter what the value of the frac is. + // this has the effect that if the smaller time is in a leap second, we can simply remove `SECOND_AS_NANOS` + // from the frac, and then calculate as normal + + match self.secs.cmp(&rhs.secs) { + // when equal, compare the frac only + // self.frac is leap, rhs.frac is not and the difference is at least a second + Ordering::Equal if self.frac >= rhs.frac + SECOND_AS_NANOS => { + Duration::new(1, self.frac - rhs.frac - SECOND_AS_NANOS) + } + // self.frac may be leap but resulting frac is not + Ordering::Equal if self.frac >= rhs.frac => Duration::new(0, self.frac - rhs.frac), + // rhs is larger and rhs.frac is leap while self.frac is not and the differnce is at least a second, + Ordering::Equal if rhs.frac >= self.frac + SECOND_AS_NANOS => { + Duration::new(1, rhs.frac - self.frac - SECOND_AS_NANOS) + } + // rhs is larger but the difference is less than a + Ordering::Equal => Duration::new(0, rhs.frac - self.frac), + Ordering::Greater => { + // must be `Forwards` + + // here we can remove SECOND_AS_NANOS from rhs.frac if it is leap + let (secs, frac) = match (rhs.frac % SECOND_AS_NANOS).cmp(&self.frac) { + // + Ordering::Less => { + (self.secs - rhs.secs, self.frac - (rhs.frac % SECOND_AS_NANOS)) + } + Ordering::Equal => (self.secs - rhs.secs, 0), + // this extra subtraction of 1 won't underflow because the self.secs are strictly greater than the rhs.secs + Ordering::Greater => ( + self.secs - rhs.secs - 1, + self.frac + SECOND_AS_NANOS - (rhs.frac % SECOND_AS_NANOS), + ), + }; + + Duration::new(secs.into(), frac) + } + Ordering::Less => { + // must be `Backwards` + + // here we can remove SECOND_AS_NANOS from self.frac if it is leap + let (secs, frac) = match (self.frac % SECOND_AS_NANOS).cmp(&rhs.frac) { + Ordering::Less => { + (rhs.secs - self.secs, rhs.frac - (self.frac % SECOND_AS_NANOS)) + } + Ordering::Equal => (rhs.secs - self.secs, 0), + // this extra subtraction of 1 won't underflow because the rhs.secs are strictly greater than the self.secs + Ordering::Greater => ( + rhs.secs - self.secs - 1, + rhs.frac + SECOND_AS_NANOS - (self.frac % SECOND_AS_NANOS), + ), + }; + + Duration::new(secs.into(), frac) + } + } + } + + /// Subtracts another `NaiveTime` from the current time. + /// + /// Returns a `Some(Duration)` within +/- 1 day, when the self time + /// is greater than or equal to the other time, otherwise returns `None`. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// use core::time::Duration; + /// + /// let from_hmsm = NaiveTime::from_hms_milli; + /// let since = NaiveTime::checked_duration_since; + /// + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), + /// Some(Duration::from_secs(0))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), + /// Some(Duration::from_millis(25))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), + /// Some(Duration::from_millis(975))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), + /// Some(Duration::from_secs(7))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), + /// Some(Duration::from_secs(5 * 60))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), + /// Some(Duration::from_secs(3 * 3600))); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), + /// None); + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), + /// Some(Duration::from_secs(3600 + 60 + 1) + Duration::from_millis(100))); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # use core::time::Duration; + /// # let from_hmsm = NaiveTime::from_hms_milli; + /// # let since = NaiveTime::checked_duration_since; + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), + /// Some(Duration::from_secs(1))); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), + /// Some(Duration::from_millis(1500))); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), + /// Some(Duration::from_secs(60))); + /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), + /// Some(Duration::from_secs(1))); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), + /// Some(Duration::from_secs(61))); + /// ``` + pub fn checked_duration_since(self, rhs: NaiveTime) -> Option { + match self >= rhs { + true => Some(self.abs_duration_since(rhs)), + false => None, + } + } + /// Formats the time with the specified formatting items. /// Otherwise it is the same as the ordinary [`format`](#method.format) method. /// @@ -1091,6 +1443,132 @@ impl SubAssign for NaiveTime { } } +/// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +/// +/// As a part of Chrono's [leap second handling](#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// use core::time::Duration; +/// +/// let from_hmsm = NaiveTime::from_hms_milli; +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(0), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(1), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(60 + 4), from_hmsm(3, 6, 11, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_millis(80), from_hmsm(3, 5, 7, 80)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::from_millis(280), from_hmsm(3, 5, 8, 230)); +/// ``` +/// +/// The addition wraps around. +/// +/// ``` +/// # use chrono::NaiveTime; +/// # use core::time::Duration; +/// # let from_hmsm = NaiveTime::from_hms_milli; +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(22*60*60), from_hmsm(1, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::from_secs(24 * 60 * 60 * 800), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::NaiveTime; +/// # use core::time::Duration; +/// # let from_hmsm = NaiveTime::from_hms_milli; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + Duration::from_secs(0), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + Duration::from_millis(500), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + Duration::from_millis(800), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + Duration::from_secs(10), from_hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + Duration::from_secs(24 * 60 * 60 * 1), from_hmsm(3, 5, 59, 300)); +/// ``` +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveTime { + self.overflowing_add_opt(rhs).unwrap().0 + } +} + +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + +/// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +/// +/// As a part of Chrono's [leap second handling](#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// use core::time::Duration; +/// +/// let from_hmsm = NaiveTime::from_hms_milli; +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(0), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(1), from_hmsm(3, 5, 6, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(60 + 5), from_hmsm(3, 4, 2, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_millis(80), from_hmsm(3, 5, 6, 920)); +/// assert_eq!(from_hmsm(3, 5, 7, 950) - Duration::from_millis(280), from_hmsm(3, 5, 7, 670)); +/// ``` +/// +/// The subtraction wraps around. +/// +/// ``` +/// # use chrono::NaiveTime; +/// # use core::time::Duration; +/// # let from_hmsm = NaiveTime::from_hms_milli; +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(8*60*60), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::from_secs(24 * 60 * 60 * 800), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::NaiveTime; +/// # use core::time::Duration; +/// # let from_hmsm = NaiveTime::from_hms_milli; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - Duration::from_secs(0), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - Duration::from_millis(200), from_hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - Duration::from_millis(500), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - Duration::from_secs(60), from_hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - Duration::from_secs(24 * 60 * 60 * 1), from_hmsm(3, 6, 0, 300)); +/// ``` +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveTime { + self.overflowing_sub_opt(rhs).unwrap().0 + } +} + +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs); + } +} + /// Subtracts another `NaiveTime` from the current time. /// Returns a `Duration` within +/- 1 day. /// This does not overflow or underflow at all. diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 0a8b26ebfc..ac1bec9bdc 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -3,8 +3,10 @@ //! The time zone which has a fixed offset from UTC. +use core::convert::TryFrom; use core::fmt; use core::ops::{Add, Sub}; +use core::time::Duration; use num_integer::div_mod_floor; #[cfg(feature = "rkyv")] @@ -12,7 +14,6 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Timelike; @@ -167,14 +168,24 @@ impl arbitrary::Arbitrary<'_> for FixedOffset { // but keep keeps the leap second information. // this should be implemented more efficiently, but for the time being, this is generic right now. -fn add_with_leapsecond(lhs: &T, rhs: i32) -> T +fn add_with_leapsecond(lhs: &T, rhs: u32) -> T where - T: Timelike + Add, + T: Timelike + Add, { // extract and temporarily remove the fractional part and later recover it let nanos = lhs.nanosecond(); let lhs = lhs.with_nanosecond(0).unwrap(); - (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() + (lhs + Duration::from_secs(rhs.into())).with_nanosecond(nanos).unwrap() +} + +fn sub_with_leapsecond(lhs: &T, rhs: u32) -> T +where + T: Timelike + Sub, +{ + // extract and temporarily remove the fractional part and later recover it + let nanos = lhs.nanosecond(); + let lhs = lhs.with_nanosecond(0).unwrap(); + (lhs - Duration::from_secs(rhs.into())).with_nanosecond(nanos).unwrap() } impl Add for NaiveTime { @@ -182,7 +193,11 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } else { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } } } @@ -191,7 +206,11 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } else { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } } } @@ -200,7 +219,11 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } else { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } } } @@ -209,7 +232,11 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } else { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } } } @@ -218,7 +245,11 @@ impl Add for DateTime { #[inline] fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } else { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } } } @@ -227,7 +258,11 @@ impl Sub for DateTime { #[inline] fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + if rhs.local_minus_utc > 0 { + sub_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc).unwrap()) + } else { + add_with_leapsecond(&self, u32::try_from(rhs.local_minus_utc.abs()).unwrap()) + } } } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index e280c78002..5c6d29cfa5 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -134,12 +134,19 @@ impl TimeZone for Local { not(any(target_os = "emscripten", target_os = "wasi")) ))] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { + use core::convert::TryInto; + let mut local = local.clone(); // Get the offset from the js runtime let offset = FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) .unwrap(); - local -= crate::Duration::seconds(offset.local_minus_utc() as i64); + if offset.local_minus_utc() > 0 { + local -= core::time::Duration::from_secs(offset.local_minus_utc().try_into().unwrap()); + } else { + local += + core::time::Duration::from_secs(offset.local_minus_utc().abs().try_into().unwrap()); + } LocalResult::Single(DateTime::from_utc(local, offset)) } @@ -185,9 +192,11 @@ impl TimeZone for Local { #[cfg(test)] mod tests { + use core::time::Duration; + use super::Local; use crate::offset::TimeZone; - use crate::{Datelike, Duration, Utc}; + use crate::{Datelike, Utc}; #[test] fn verify_correct_offsets() { @@ -204,8 +213,8 @@ mod tests { #[test] fn verify_correct_offsets_distant_past() { - // let distant_past = Local::now() - Duration::days(365 * 100); - let distant_past = Local::now() - Duration::days(250 * 31); + let distant_past = Local::now() - Duration::from_secs(24 * 60 * 60 * 250 * 31); + let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); @@ -218,7 +227,8 @@ mod tests { #[test] fn verify_correct_offsets_distant_future() { - let distant_future = Local::now() + Duration::days(250 * 31); + let distant_future = Local::now() + Duration::from_secs(24 * 60 * 60 * 250 * 31); + let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); diff --git a/src/oldtime.rs b/src/oldtime.rs index f935e9a4b1..08c4168414 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -19,6 +19,8 @@ use std::error::Error; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use crate::TimeDelta; + /// The number of nanoseconds in a microsecond. const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. @@ -70,6 +72,14 @@ pub(crate) const MAX: Duration = Duration { }; impl Duration { + pub(crate) fn as_time_delta(&self) -> Option { + if self.secs < 0 { + Some(TimeDelta::Backwards(self.abs().to_std().ok()?)) + } else { + Some(TimeDelta::Forwards(self.to_std().ok()?)) + } + } + /// Makes a new `Duration` with given number of weeks. /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. /// Panics when the duration is out of bounds. diff --git a/src/round.rs b/src/round.rs index b95f245708..7b163a7069 100644 --- a/src/round.rs +++ b/src/round.rs @@ -2,14 +2,17 @@ // See README.md and LICENSE.txt for details. use crate::datetime::DateTime; -use crate::oldtime::Duration; +use crate::oldtime::Duration as OldDuration; use crate::NaiveDateTime; use crate::TimeZone; use crate::Timelike; + use core::cmp::Ordering; +use core::convert::TryFrom; use core::fmt; use core::marker::Sized; use core::ops::{Add, Sub}; +use core::time::Duration; /// Extension trait for subsecond rounding or truncation to a maximum number /// of digits. Rounding can be used to decrease the error variance when @@ -17,6 +20,7 @@ use core::ops::{Add, Sub}; /// behavior in Chrono display formatting. Either can be used to guarantee /// equality (e.g. for testing) when round-tripping through a lower precision /// format. +#[deprecated(since = "0.4.24", note = "Use CoreSubsecRound instead")] pub trait SubsecRound { /// Return a copy rounded to the specified number of subsecond digits. With /// 9 or more digits, self is returned unmodified. Halfway values are @@ -44,7 +48,71 @@ pub trait SubsecRound { fn trunc_subsecs(self, digits: u16) -> Self; } +#[allow(deprecated)] impl SubsecRound for T +where + T: Timelike + Add + Sub, +{ + fn round_subsecs(self, digits: u16) -> T { + let span = span_for_digits(digits); + let delta_down = self.nanosecond() % span; + if delta_down > 0 { + let delta_up = span - delta_down; + if delta_up <= delta_down { + self + OldDuration::nanoseconds(delta_up.into()) + } else { + self - OldDuration::nanoseconds(delta_down.into()) + } + } else { + self // unchanged + } + } + + fn trunc_subsecs(self, digits: u16) -> T { + let span = span_for_digits(digits); + let delta_down = self.nanosecond() % span; + if delta_down > 0 { + self - OldDuration::nanoseconds(delta_down.into()) + } else { + self // unchanged + } + } +} + +/// Extension trait for subsecond rounding or truncation to a maximum number +/// of digits. Rounding can be used to decrease the error variance when +/// serializing/persisting to lower precision. Truncation is the default +/// behavior in Chrono display formatting. Either can be used to guarantee +/// equality (e.g. for testing) when round-tripping through a lower precision +/// format. +pub trait CoreSubsecRound { + /// Return a copy rounded to the specified number of subsecond digits. With + /// 9 or more digits, self is returned unmodified. Halfway values are + /// rounded up (away from zero). + /// + /// # Example + /// ``` rust + /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); + /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); + /// ``` + fn round_subsecs(self, digits: u16) -> Self; + + /// Return a copy truncated to the specified number of subsecond + /// digits. With 9 or more digits, self is returned unmodified. + /// + /// # Example + /// ``` rust + /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; + /// let dt = Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap(); + /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); + /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); + /// ``` + fn trunc_subsecs(self, digits: u16) -> Self; +} + +impl CoreSubsecRound for T where T: Timelike + Add + Sub, { @@ -54,9 +122,9 @@ where if delta_down > 0 { let delta_up = span - delta_down; if delta_up <= delta_down { - self + Duration::nanoseconds(delta_up.into()) + self + Duration::from_nanos(delta_up.into()) } else { - self - Duration::nanoseconds(delta_down.into()) + self - Duration::from_nanos(delta_down.into()) } } else { self // unchanged @@ -67,7 +135,7 @@ where let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { - self - Duration::nanoseconds(delta_down.into()) + self - Duration::from_nanos(delta_down.into()) } else { self // unchanged } @@ -94,10 +162,10 @@ fn span_for_digits(digits: u16) -> u32 { /// Extension trait for rounding or truncating a DateTime by a Duration. /// /// # Limitations -/// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and +/// Both rounding and truncating are done via [`OldDuration::num_nanoseconds`] and /// [`DateTime::timestamp_nanos`]. This means that they will fail if either the -/// `Duration` or the `DateTime` are too big to represented as nanoseconds. They -/// will also fail if the `Duration` is bigger than the timestamp. +/// `OldDuration` or the `DateTime` are too big to represented as nanoseconds. They +/// will also fail if the `OldDuration` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating #[cfg(any(feature = "std", test))] @@ -107,39 +175,77 @@ pub trait DurationRound: Sized { #[cfg(not(any(feature = "std", test)))] type Err: fmt::Debug + fmt::Display; - /// Return a copy rounded by Duration. + /// Return a copy rounded by OldDuration. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// # use chrono::{DateTime, DurationRound, Duration as OldDuration, TimeZone, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( - /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_round(OldDuration::milliseconds(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_round(Duration::days(1)).unwrap().to_string(), + /// dt.duration_round(OldDuration::days(1)).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` - fn duration_round(self, duration: Duration) -> Result; + #[deprecated(since = "0.4.24", note = "Use core_duration_round() instead")] + fn duration_round(self, duration: OldDuration) -> Result; - /// Return a copy truncated by Duration. + /// Return a copy rounded by a core::time::Duration. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc, NaiveDate}; + /// use core::time::Duration; + /// # use chrono::{DateTime, DurationRound, TimeZone, Utc, NaiveDate}; /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); /// assert_eq!( - /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.core_duration_round(Duration::from_millis(10)).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + /// dt.core_duration_round(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + /// "2018-01-12 00:00:00 UTC" + /// ); + /// ``` + fn core_duration_round(self, duration: Duration) -> Result; + + /// Return a copy truncated by OldDuration. + /// + /// # Example + /// ``` rust + /// # use chrono::{DateTime, DurationRound, Duration as OldDuration, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!( + /// dt.duration_trunc(OldDuration::milliseconds(10)).unwrap().to_string(), + /// "2018-01-11 12:00:00.150 UTC" + /// ); + /// assert_eq!( + /// dt.duration_trunc(OldDuration::days(1)).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` - fn duration_trunc(self, duration: Duration) -> Result; + #[deprecated(since = "0.4.24", note = "Use core_duration_trunc() instead")] + fn duration_trunc(self, duration: OldDuration) -> Result; + + /// Return a copy truncated by core::time::Duration. + /// + /// # Example + /// ``` rust + /// use core::time::Duration; + /// # use chrono::{DateTime, DurationRound, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11).unwrap().and_hms_milli_opt(12, 0, 0, 154).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!( + /// dt.core_duration_trunc(Duration::from_millis(10)).unwrap().to_string(), + /// "2018-01-11 12:00:00.150 UTC" + /// ); + /// assert_eq!( + /// dt.core_duration_trunc(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + /// "2018-01-11 00:00:00 UTC" + /// ); + /// ``` + fn core_duration_trunc(self, duration: Duration) -> Result; } /// The maximum number of seconds a DateTime can be to be represented as nanoseconds @@ -148,34 +254,51 @@ const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; impl DurationRound for DateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: OldDuration) -> Result { duration_round(self.naive_local(), self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: OldDuration) -> Result { duration_trunc(self.naive_local(), self, duration) } + + fn core_duration_round(self, duration: Duration) -> Result { + core_duration_round(self.naive_local(), self, duration) + } + + fn core_duration_trunc(self, duration: Duration) -> Result { + core_duration_trunc(self.naive_local(), self, duration) + } } impl DurationRound for NaiveDateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { + fn duration_round(self, duration: OldDuration) -> Result { duration_round(self, self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { + fn duration_trunc(self, duration: OldDuration) -> Result { duration_trunc(self, self, duration) } + + fn core_duration_round(self, duration: Duration) -> Result { + core_duration_round(self, self, duration) + } + + fn core_duration_trunc(self, duration: Duration) -> Result { + core_duration_trunc(self, self, duration) + } } +#[allow(deprecated)] fn duration_round( naive: NaiveDateTime, original: T, - duration: Duration, + duration: OldDuration, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { @@ -198,9 +321,9 @@ where (span - delta_down, delta_down) }; if delta_up <= delta_down { - Ok(original + Duration::nanoseconds(delta_up)) + Ok(original + OldDuration::nanoseconds(delta_up)) } else { - Ok(original - Duration::nanoseconds(delta_down)) + Ok(original - OldDuration::nanoseconds(delta_down)) } } } else { @@ -208,13 +331,14 @@ where } } +#[allow(deprecated)] fn duration_trunc( naive: NaiveDateTime, original: T, - duration: Duration, + duration: OldDuration, ) -> Result where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { if let Some(span) = duration.num_nanoseconds() { if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { @@ -227,14 +351,106 @@ where let delta_down = stamp % span; match delta_down.cmp(&0) { Ordering::Equal => Ok(original), - Ordering::Greater => Ok(original - Duration::nanoseconds(delta_down)), - Ordering::Less => Ok(original - Duration::nanoseconds(span - delta_down.abs())), + Ordering::Greater => Ok(original - OldDuration::nanoseconds(delta_down)), + Ordering::Less => Ok(original - OldDuration::nanoseconds(span - delta_down.abs())), } } else { Err(RoundingError::DurationExceedsLimit) } } +fn core_duration_round( + naive: NaiveDateTime, + original: T, + duration: Duration, +) -> Result +where + T: Timelike + Add + Sub, +{ + let span = + i128::try_from(duration.as_nanos()).map_err(|_| RoundingError::TimestampExceedsLimit)?; + + if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { + return Err(RoundingError::TimestampExceedsLimit); + } + let stamp = i128::from(naive.timestamp_nanos()); + if span > stamp.abs() { + return Err(RoundingError::DurationExceedsTimestamp); + } + if span == 0 { + return Ok(original); + } + let delta_down = stamp % span; + + if delta_down == 0 { + Ok(original) + } else { + let (delta_up, delta_down) = if delta_down < 0 { + (delta_down.abs(), span - delta_down.abs()) + } else { + (span - delta_down, delta_down) + }; + + if delta_up <= delta_down { + let seconds_part = u64::try_from(delta_up / 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + let nanos_part = u32::try_from(delta_up % 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + + Ok(original + Duration::new(seconds_part, nanos_part)) + } else { + let seconds_part = u64::try_from(delta_down / 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + let nanos_part = u32::try_from(delta_down % 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + + Ok(original - Duration::new(seconds_part, nanos_part)) + } + } +} + +fn core_duration_trunc( + naive: NaiveDateTime, + original: T, + duration: Duration, +) -> Result +where + T: Timelike + Add + Sub, +{ + let span = + i128::try_from(duration.as_nanos()).map_err(|_| RoundingError::TimestampExceedsLimit)?; + + if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { + return Err(RoundingError::TimestampExceedsLimit); + } + + let stamp = i128::from(naive.timestamp_nanos()); + + if span > stamp.abs() { + return Err(RoundingError::DurationExceedsTimestamp); + } + + let delta_down = stamp % span; + + match delta_down.cmp(&0) { + Ordering::Equal => Ok(original), + Ordering::Greater => { + let seconds_part = u64::try_from(delta_down / 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + let nanos_part = u32::try_from(delta_down % 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + Ok(original - Duration::new(seconds_part, nanos_part)) + } + Ordering::Less => { + let seconds_part = u64::try_from((span - delta_down.abs()) / 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + let nanos_part = u32::try_from((span - delta_down.abs()) % 1_000_000_000) + .map_err(|_| RoundingError::TimestampExceedsLimit)?; + Ok(original - Duration::new(seconds_part, nanos_part)) + } + } +} + /// An error from rounding by `Duration` /// /// See: [`DurationRound`] @@ -243,11 +459,12 @@ pub enum RoundingError { /// Error when the Duration exceeds the Duration from or until the Unix epoch. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// use core::time::Duration; + /// # use chrono::{DateTime, DurationRound, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(1970, 12, 12, 0, 0, 0).unwrap(); /// /// assert_eq!( - /// dt.duration_round(Duration::days(365)), + /// dt.core_duration_round(Duration::from_secs(365 * 24 * 60 * 60)), /// Err(RoundingError::DurationExceedsTimestamp), /// ); /// ``` @@ -264,15 +481,17 @@ pub enum RoundingError { /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` + #[deprecated(since = "0.4.24", note = "No longer needed")] DurationExceedsLimit, /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; + /// use core::time::Duration; + /// # use chrono::{DateTime, DurationRound, RoundingError, TimeZone, Utc}; /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// - /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); + /// assert_eq!(dt.core_duration_round(Duration::from_secs(24 * 60 * 60)), Err(RoundingError::TimestampExceedsLimit),); /// ``` TimestampExceedsLimit, } @@ -283,6 +502,7 @@ impl fmt::Display for RoundingError { RoundingError::DurationExceedsTimestamp => { write!(f, "duration in nanoseconds exceeds timestamp") } + #[allow(deprecated)] RoundingError::DurationExceedsLimit => { write!(f, "duration exceeds num_nanoseconds limit") } @@ -304,13 +524,18 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { - use super::{Duration, DurationRound, SubsecRound}; + use core::time::Duration; + + use super::{DurationRound, OldDuration}; use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::NaiveDate; use crate::Timelike; #[test] + #[allow(deprecated)] fn test_round_subsecs() { + use super::SubsecRound; + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); let dt = pst .from_local_datetime( @@ -354,7 +579,43 @@ mod tests { } #[test] + fn test_round_core_subsecs() { + use super::CoreSubsecRound; + + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); + + assert_eq!(dt.round_subsecs(10), dt); + assert_eq!(dt.round_subsecs(9), dt); + assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680); + assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700); + assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000); + assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000); + assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000); + assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000); + assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000); + assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000); + + assert_eq!(dt.round_subsecs(0).nanosecond(), 0); + assert_eq!(dt.round_subsecs(0).second(), 13); + + let dt = + Utc.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); + assert_eq!(dt.round_subsecs(9), dt); + assert_eq!(dt.round_subsecs(4), dt); + assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); + assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000); + assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000); + + assert_eq!(dt.round_subsecs(0).nanosecond(), 0); + assert_eq!(dt.round_subsecs(0).second(), 28); + } + + #[test] + #[allow(deprecated)] fn test_round_leap_nanos() { + use super::SubsecRound; + let dt = Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2016, 12, 31) @@ -363,6 +624,7 @@ mod tests { .unwrap(), ) .unwrap(); + assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); @@ -374,7 +636,26 @@ mod tests { } #[test] + fn test_round_core_leap_nanos() { + use super::CoreSubsecRound; + + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); + assert_eq!(dt.round_subsecs(9), dt); + assert_eq!(dt.round_subsecs(4), dt); + assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); + assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000); + assert_eq!(dt.round_subsecs(1).second(), 59); + + assert_eq!(dt.round_subsecs(0).nanosecond(), 0); + assert_eq!(dt.round_subsecs(0).second(), 0); + } + + #[test] + #[allow(deprecated)] fn test_trunc_subsecs() { + use super::SubsecRound; + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); let dt = pst .from_local_datetime( @@ -418,7 +699,43 @@ mod tests { } #[test] + fn test_trunc_core_subsecs() { + use super::CoreSubsecRound; + + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 13, 84_660_684).unwrap(); + + assert_eq!(dt.trunc_subsecs(10), dt); + assert_eq!(dt.trunc_subsecs(9), dt); + assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680); + assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600); + assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000); + assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000); + assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000); + assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000); + assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000); + assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0); + + assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); + assert_eq!(dt.trunc_subsecs(0).second(), 13); + + let dt = + pst.ymd_opt(2018, 1, 11).unwrap().and_hms_nano_opt(10, 5, 27, 750_500_000).unwrap(); + assert_eq!(dt.trunc_subsecs(9), dt); + assert_eq!(dt.trunc_subsecs(4), dt); + assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); + assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000); + assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000); + + assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); + assert_eq!(dt.trunc_subsecs(0).second(), 27); + } + + #[test] + #[allow(deprecated)] fn test_trunc_leap_nanos() { + use super::SubsecRound; + let dt = Utc .from_local_datetime( &NaiveDate::from_ymd_opt(2016, 12, 31) @@ -427,6 +744,23 @@ mod tests { .unwrap(), ) .unwrap(); + + assert_eq!(dt.trunc_subsecs(9), dt); + assert_eq!(dt.trunc_subsecs(4), dt); + assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); + assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000); + assert_eq!(dt.trunc_subsecs(1).second(), 59); + + assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000); + assert_eq!(dt.trunc_subsecs(0).second(), 59); + } + + #[test] + fn test_trunc_core_leap_nanos() { + use super::CoreSubsecRound; + + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 1_750_500_000).unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); @@ -438,6 +772,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_duration_round() { let dt = Utc .from_local_datetime( @@ -449,12 +784,12 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_round(Duration::zero()).unwrap().to_string(), + dt.duration_round(OldDuration::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500 UTC" ); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(OldDuration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); @@ -468,7 +803,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down @@ -481,24 +816,24 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(30)).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(OldDuration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(OldDuration::days(1)).unwrap().to_string(), "2012-12-13 00:00:00 UTC" ); @@ -506,11 +841,11 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(OldDuration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 +01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(OldDuration::weeks(1)).unwrap().to_string(), "2020-10-29 00:00:00 +01:00" ); @@ -518,16 +853,95 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(OldDuration::days(1)).unwrap().to_string(), "2020-10-28 00:00:00 -01:00" ); assert_eq!( - dt.duration_round(Duration::weeks(1)).unwrap().to_string(), + dt.duration_round(OldDuration::weeks(1)).unwrap().to_string(), "2020-10-29 00:00:00 -01:00" ); } #[test] + fn test_core_duration_round() { + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); + + assert_eq!( + dt.core_duration_round(Duration::from_secs(0)).unwrap().to_string(), + "2016-12-31 23:59:59.175500 UTC" + ); + + assert_eq!( + dt.core_duration_round(Duration::from_millis(10)).unwrap().to_string(), + "2016-12-31 23:59:59.180 UTC" + ); + + // round up + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 5)).unwrap().to_string(), + "2012-12-12 18:25:00 UTC" + ); + // round down + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 5)).unwrap().to_string(), + "2012-12-12 18:20:00 UTC" + ); + + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 10)).unwrap().to_string(), + "2012-12-12 18:20:00 UTC" + ); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 30)).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 60)).unwrap().to_string(), + "2012-12-12 18:00:00 UTC" + ); + assert_eq!( + dt.core_duration_round(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2012-12-13 00:00:00 UTC" + ); + + // timezone east + let dt = FixedOffset::east_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2020-10-28 00:00:00 +01:00" + ); + assert_eq!( + dt.core_duration_round(Duration::from_secs(7 * 24 * 60 * 60)).unwrap().to_string(), + "2020-10-29 00:00:00 +01:00" + ); + + // timezone west + let dt = FixedOffset::west_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2020-10-28 00:00:00 -01:00" + ); + assert_eq!( + dt.core_duration_round(Duration::from_secs(7 * 24 * 60 * 60)).unwrap().to_string(), + "2020-10-29 00:00:00 -01:00" + ); + } + + #[test] + #[allow(deprecated)] fn test_duration_round_naive() { let dt = Utc .from_local_datetime( @@ -540,12 +954,12 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_round(Duration::zero()).unwrap().to_string(), + dt.duration_round(OldDuration::zero()).unwrap().to_string(), "2016-12-31 23:59:59.175500" ); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(OldDuration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.180" ); @@ -560,7 +974,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:25:00" ); // round down @@ -574,38 +988,109 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(5)).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + + assert_eq!( + dt.duration_round(OldDuration::minutes(10)).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_round(OldDuration::minutes(30)).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round(OldDuration::hours(1)).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_round(OldDuration::days(1)).unwrap().to_string(), + "2012-12-13 00:00:00" + ); + } + + #[test] + fn test_core_duration_round_naive() { + let dt = Utc + .ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.core_duration_round(Duration::from_secs(0)).unwrap().to_string(), + "2016-12-31 23:59:59.175500" + ); + + assert_eq!( + dt.core_duration_round(Duration::from_millis(10)).unwrap().to_string(), + "2016-12-31 23:59:59.180" + ); + + // round up + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .naive_utc(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 5)).unwrap().to_string(), + "2012-12-12 18:25:00" + ); + // round down + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap() + .naive_utc(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.core_duration_round(Duration::from_secs(60 * 10)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.core_duration_round(Duration::from_secs(60 * 30)).unwrap().to_string(), "2012-12-12 18:30:00" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.core_duration_round(Duration::from_secs(60 * 60)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.core_duration_round(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), "2012-12-13 00:00:00" ); } #[test] + #[allow(deprecated)] fn test_duration_round_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(OldDuration::minutes(10)).unwrap().to_string(), + "1969-12-12 12:10:00 UTC" + ); + } + + #[test] + fn test_core_duration_round_pre_epoch() { + let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); + assert_eq!( + dt.core_duration_round(Duration::from_secs(60 * 10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } #[test] + #[allow(deprecated)] fn test_duration_trunc() { let dt = Utc .from_local_datetime( @@ -617,7 +1102,7 @@ mod tests { .unwrap(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(OldDuration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" ); @@ -631,7 +1116,7 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down @@ -644,23 +1129,23 @@ mod tests { ) .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(10)).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(30)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::hours(1)).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::days(1)).unwrap().to_string(), "2012-12-12 00:00:00 UTC" ); @@ -668,11 +1153,11 @@ mod tests { let dt = FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 +01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::weeks(1)).unwrap().to_string(), "2020-10-22 00:00:00 +01:00" ); @@ -680,16 +1165,89 @@ mod tests { let dt = FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::days(1)).unwrap().to_string(), "2020-10-27 00:00:00 -01:00" ); assert_eq!( - dt.duration_trunc(Duration::weeks(1)).unwrap().to_string(), + dt.duration_trunc(OldDuration::weeks(1)).unwrap().to_string(), "2020-10-22 00:00:00 -01:00" ); } #[test] + fn test_core_duration_trunc() { + let dt = + Utc.ymd_opt(2016, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 175_500_000).unwrap(); + + assert_eq!( + dt.core_duration_trunc(Duration::from_millis(10)).unwrap().to_string(), + "2016-12-31 23:59:59.170 UTC" + ); + + // would round up + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 30, 0).unwrap(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 5)).unwrap().to_string(), + "2012-12-12 18:20:00 UTC" + ); + // would round down + let dt = Utc.ymd_opt(2012, 12, 12).unwrap().and_hms_milli_opt(18, 22, 29, 999).unwrap(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 5)).unwrap().to_string(), + "2012-12-12 18:20:00 UTC" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 10)).unwrap().to_string(), + "2012-12-12 18:20:00 UTC" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 30)).unwrap().to_string(), + "2012-12-12 18:00:00 UTC" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 60)).unwrap().to_string(), + "2012-12-12 18:00:00 UTC" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2012-12-12 00:00:00 UTC" + ); + + // timezone east + let dt = FixedOffset::east_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2020-10-27 00:00:00 +01:00" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(7 * 24 * 60 * 60)).unwrap().to_string(), + "2020-10-22 00:00:00 +01:00" + ); + + // timezone west + let dt = FixedOffset::west_opt(3600) + .unwrap() + .ymd_opt(2020, 10, 27) + .unwrap() + .and_hms_opt(15, 0, 0) + .unwrap(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), + "2020-10-27 00:00:00 -01:00" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(7 * 24 * 60 * 60)).unwrap().to_string(), + "2020-10-22 00:00:00 -01:00" + ); + } + + #[test] + #[allow(deprecated)] fn test_duration_trunc_naive() { let dt = Utc .from_local_datetime( @@ -702,7 +1260,7 @@ mod tests { .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(OldDuration::milliseconds(10)).unwrap().to_string(), "2016-12-31 23:59:59.170" ); @@ -717,7 +1275,7 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(5)).unwrap().to_string(), "2012-12-12 18:20:00" ); // would round down @@ -731,32 +1289,96 @@ mod tests { .unwrap() .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(5)).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_trunc(OldDuration::minutes(10)).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_trunc(OldDuration::minutes(30)).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_trunc(OldDuration::hours(1)).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_trunc(OldDuration::days(1)).unwrap().to_string(), + "2012-12-12 00:00:00" + ); + } + + #[test] + fn test_core_duration_trunc_naive() { + let dt = Utc + .ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.core_duration_trunc(Duration::from_millis(10)).unwrap().to_string(), + "2016-12-31 23:59:59.170" + ); + + // would round up + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .naive_utc(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 5)).unwrap().to_string(), "2012-12-12 18:20:00" ); + // would round down + let dt = Utc + .ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap() + .naive_utc(); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.core_duration_trunc(Duration::from_secs(60 * 5)).unwrap().to_string(), "2012-12-12 18:20:00" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.core_duration_trunc(Duration::from_secs(60 * 10)).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 30)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.core_duration_trunc(Duration::from_secs(60 * 60)).unwrap().to_string(), "2012-12-12 18:00:00" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.core_duration_trunc(Duration::from_secs(24 * 60 * 60)).unwrap().to_string(), "2012-12-12 00:00:00" ); } #[test] + #[allow(deprecated)] fn test_duration_trunc_pre_epoch() { let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(OldDuration::minutes(10)).unwrap().to_string(), + "1969-12-12 12:10:00 UTC" + ); + } + + #[test] + fn test_core_duration_trunc_pre_epoch() { + let dt = Utc.ymd_opt(1969, 12, 12).unwrap().and_hms_opt(12, 12, 12).unwrap(); + assert_eq!( + dt.core_duration_trunc(Duration::from_secs(60 * 10)).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } diff --git a/src/traits.rs b/src/traits.rs index 1b6af6926b..c316906a02 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -180,7 +180,7 @@ pub trait Timelike: Sized { #[cfg(test)] mod tests { use super::Datelike; - use crate::{Duration, NaiveDate}; + use crate::{Days, NaiveDate}; /// Tests `Datelike::num_days_from_ce` against an alternative implementation. /// @@ -229,7 +229,7 @@ mod tests { "on {:?}", jan1_year ); - let mid_year = jan1_year + Duration::days(133); + let mid_year = jan1_year + Days::new(133); assert_eq!( mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 130649a71f..83df11e9e6 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -2,7 +2,7 @@ use chrono::offset::TimeZone; use chrono::Local; use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; -use std::{path, process}; +use std::{path, process, time::Duration}; #[cfg(unix)] fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { @@ -71,6 +71,6 @@ fn try_verify_against_date_command() { verify_against_date_command_local(date_path, date); } - date += chrono::Duration::hours(1); + date += Duration::from_secs(60 * 60); } }