From f0c62b44941accf55b37d20b714fed7d3d566bfd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 11:15:12 +0200 Subject: [PATCH 01/57] Use `expect` instead of `unwrap` in `Add` and `Sub` impls --- src/datetime/mod.rs | 14 ++++++++------ src/naive/date.rs | 8 ++++---- src/naive/datetime/mod.rs | 12 ++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 59178743dc..b05744e653 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1252,7 +1252,8 @@ impl Add for DateTime { #[inline] fn add(mut self, rhs: FixedOffset) -> DateTime { - self.datetime = self.naive_utc().checked_add_offset(rhs).unwrap(); + self.datetime = + self.naive_utc().checked_add_offset(rhs).expect("`DateTime + FixedOffset` overflowed"); self } } @@ -1261,7 +1262,7 @@ impl Add for DateTime { type Output = DateTime; fn add(self, rhs: Months) -> Self::Output { - self.checked_add_months(rhs).unwrap() + self.checked_add_months(rhs).expect("`DateTime + Months` out of range") } } @@ -1309,7 +1310,8 @@ impl Sub for DateTime { #[inline] fn sub(mut self, rhs: FixedOffset) -> DateTime { - self.datetime = self.naive_utc().checked_sub_offset(rhs).unwrap(); + self.datetime = + self.naive_utc().checked_sub_offset(rhs).expect("`DateTime - FixedOffset` overflowed"); self } } @@ -1318,7 +1320,7 @@ impl Sub for DateTime { type Output = DateTime; fn sub(self, rhs: Months) -> Self::Output { - self.checked_sub_months(rhs).unwrap() + self.checked_sub_months(rhs).expect("`DateTime - Months` out of range") } } @@ -1344,7 +1346,7 @@ impl Add for DateTime { type Output = DateTime; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`DateTime + Days` out of range") } } @@ -1352,7 +1354,7 @@ impl Sub for DateTime { type Output = DateTime; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`DateTime - Days` out of range") } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 700958e171..4b3cbe84bd 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1889,7 +1889,7 @@ impl Add for NaiveDate { /// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); /// ``` fn add(self, months: Months) -> Self::Output { - self.checked_add_months(months).unwrap() + self.checked_add_months(months).expect("`NaiveDate + Months` out of range") } } @@ -1914,7 +1914,7 @@ impl Sub for NaiveDate { /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); /// ``` fn sub(self, months: Months) -> Self::Output { - self.checked_sub_months(months).unwrap() + self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") } } @@ -1922,7 +1922,7 @@ impl Add for NaiveDate { type Output = NaiveDate; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`NaiveDate + Days` out of range") } } @@ -1930,7 +1930,7 @@ impl Sub for NaiveDate { type Output = NaiveDate; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`NaiveDate - Days` out of range") } } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 67e12c71f7..69ae76613b 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1624,7 +1624,7 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_add_offset(rhs).unwrap() + self.checked_add_offset(rhs).expect("`NaiveDateTime + FixedOffset` out of range") } } @@ -1668,7 +1668,7 @@ impl Add for NaiveDateTime { /// ); /// ``` fn add(self, rhs: Months) -> Self::Output { - Self { date: self.date.checked_add_months(rhs).unwrap(), time: self.time } + self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") } } @@ -1760,7 +1760,7 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - self.checked_sub_offset(rhs).unwrap() + self.checked_sub_offset(rhs).expect("`NaiveDateTime - FixedOffset` out of range") } } @@ -1792,7 +1792,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, rhs: Months) -> Self::Output { - Self { date: self.date.checked_sub_months(rhs).unwrap(), time: self.time } + self.checked_sub_months(rhs).expect("`NaiveDateTime - Months` out of range") } } @@ -1848,7 +1848,7 @@ impl Add for NaiveDateTime { type Output = NaiveDateTime; fn add(self, days: Days) -> Self::Output { - self.checked_add_days(days).unwrap() + self.checked_add_days(days).expect("`NaiveDateTime + Days` out of range") } } @@ -1856,7 +1856,7 @@ impl Sub for NaiveDateTime { type Output = NaiveDateTime; fn sub(self, days: Days) -> Self::Output { - self.checked_sub_days(days).unwrap() + self.checked_sub_days(days).expect("`NaiveDateTime - Days` out of range") } } From 483da3b8343b1de43681d4530a71450703e3e4aa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 16:33:53 +0200 Subject: [PATCH 02/57] Clamp `std::time::Duration` before converting to prevent panic --- src/naive/time/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index b9de19d0cb..46a3ca4697 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1165,18 +1165,19 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: Duration) -> NaiveTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.overflowing_add_signed(rhs).0 + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `chrono::Duration`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + self.overflowing_add_signed(d).0 } } impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - *self += rhs; + *self = *self + rhs; } } @@ -1256,18 +1257,19 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: Duration) -> NaiveTime { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - self.overflowing_sub_signed(rhs).0 + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `chrono::Duration`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = OldDuration::from_std(Duration::new(secs, rhs.subsec_nanos())).unwrap(); + self.overflowing_sub_signed(d).0 } } impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { - let rhs = OldDuration::from_std(rhs) - .expect("overflow converting from core::time::Duration to chrono::Duration"); - *self -= rhs; + *self = *self - rhs; } } From 78230395a6facb88085d2ec16d5253e50ca3c546 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:21:05 +0200 Subject: [PATCH 03/57] Move doc comments from method to trait (for consistency) --- src/naive/date.rs | 74 +++++++++++++++++++-------------------- src/naive/datetime/mod.rs | 72 ++++++++++++++++++------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 4b3cbe84bd..3b41640f8c 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1865,54 +1865,54 @@ impl AddAssign for NaiveDate { } } +/// An addition of months to `NaiveDate` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, Months}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); +/// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); +/// ``` impl Add for NaiveDate { type Output = NaiveDate; - /// An addition of months to `NaiveDate` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Months}; - /// - /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - /// - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); - /// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); - /// ``` fn add(self, months: Months) -> Self::Output { self.checked_add_months(months).expect("`NaiveDate + Months` out of range") } } +/// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, Months}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); +/// ``` impl Sub for NaiveDate { type Output = NaiveDate; - /// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{NaiveDate, Months}; - /// - /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); - /// - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); - /// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); - /// ``` fn sub(self, months: Months) -> Self::Output { self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 69ae76613b..06ddf7ebb1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1628,45 +1628,45 @@ impl Add for NaiveDateTime { } } +/// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), +/// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), +/// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// ); +/// ``` impl Add for NaiveDateTime { type Output = NaiveDateTime; - /// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. - /// - /// # Panics - /// - /// Panics if the resulting date would be out of range. - /// - /// # Example - /// - /// ``` - /// use chrono::{Months, NaiveDate}; - /// - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() + Months::new(11), - /// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() + Months::new(12), - /// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() + Months::new(13), - /// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() - /// ); - /// assert_eq!( - /// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() + Months::new(1), - /// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() - /// ); - /// ``` fn add(self, rhs: Months) -> Self::Output { self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") } From e3091d1b3e15c967a4b90d02f64ca9da2e7a268c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:22:05 +0200 Subject: [PATCH 04/57] Consistently document `Add` and `Sub` impls of `NaiveDate` --- src/naive/date.rs | 65 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 3b41640f8c..9677b0c439 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1826,10 +1826,15 @@ impl Datelike for NaiveDate { } } -/// An addition of `Duration` to `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. +/// Add `chrono::Duration` to `NaiveDate`. /// -/// Panics on underflow or overflow. Use [`NaiveDate::checked_add_signed`] to detect that. +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. /// /// # Example /// @@ -1858,6 +1863,15 @@ impl Add for NaiveDate { } } +/// Add-assign of `chrono::Duration` to `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDate { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1865,11 +1879,15 @@ impl AddAssign for NaiveDate { } } -/// An addition of months to `NaiveDate` clamped to valid days in resulting month. +/// Add `Months` to `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_months` to get an `Option` instead. /// /// # Example /// @@ -1893,11 +1911,15 @@ impl Add for NaiveDate { } } -/// A subtraction of Months from `NaiveDate` clamped to valid days in resulting month. +/// Subtract `Months` from `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_sub_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_months` to get an `Option` instead. /// /// # Example /// @@ -1918,6 +1940,12 @@ impl Sub for NaiveDate { } } +/// Add `Days` to `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_days` to get an `Option` instead. impl Add for NaiveDate { type Output = NaiveDate; @@ -1926,6 +1954,12 @@ impl Add for NaiveDate { } } +/// Subtract `Days` from `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_days` to get an `Option` instead. impl Sub for NaiveDate { type Output = NaiveDate; @@ -1934,11 +1968,16 @@ impl Sub for NaiveDate { } } -/// A subtraction of `Duration` from `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. +/// Subtract `chrono::Duration` from `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. /// It is the same as the addition with a negated `Duration`. /// -/// Panics on underflow or overflow. Use [`NaiveDate::checked_sub_signed`] to detect that. +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. /// /// # Example /// @@ -1967,6 +2006,16 @@ impl Sub for NaiveDate { } } +/// Subtract-assign `chrono::Duration` from `NaiveDate`. +/// +/// This discards the fractional days in `Duration`, rounding to the closest integral number of days +/// towards `Duration::zero()`. +/// It is the same as the addition with a negated `Duration`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDate { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { From ea45eeded7fe48d738c714f6f4b528ddd76b2b7d Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 16:13:15 +0200 Subject: [PATCH 05/57] Document `Add` and `Sub` impls of `NaiveTime` --- src/naive/time/mod.rs | 44 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 46a3ca4697..8418ffc29c 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1094,7 +1094,9 @@ impl Timelike for NaiveTime { } } -/// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. +/// Add `chrono::Duration` to `NaiveTime`. +/// +/// This 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], the addition assumes that **there is no leap @@ -1153,6 +1155,10 @@ impl Add for NaiveTime { } } +/// Add-assign `chrono::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1160,6 +1166,10 @@ impl AddAssign for NaiveTime { } } +/// Add `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl Add for NaiveTime { type Output = NaiveTime; @@ -1174,6 +1184,10 @@ impl Add for NaiveTime { } } +/// Add-assign `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl AddAssign for NaiveTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1181,6 +1195,10 @@ impl AddAssign for NaiveTime { } } +/// Add `FixedOffset` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. impl Add for NaiveTime { type Output = NaiveTime; @@ -1190,9 +1208,11 @@ impl Add for NaiveTime { } } -/// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// It is the same as the addition with a negated `Duration`. +/// Subtract `chrono::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +/// This is the same as addition with a negated `Duration`. /// /// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap /// second ever**, except when the `NaiveTime` itself represents a leap second in which case the @@ -1245,6 +1265,10 @@ impl Sub for NaiveTime { } } +/// Subtract-assign `chrono::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1252,6 +1276,10 @@ impl SubAssign for NaiveTime { } } +/// Subtract `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl Sub for NaiveTime { type Output = NaiveTime; @@ -1266,6 +1294,10 @@ impl Sub for NaiveTime { } } +/// Subtract-assign `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl SubAssign for NaiveTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1273,6 +1305,10 @@ impl SubAssign for NaiveTime { } } +/// Subtract `FixedOffset` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. impl Sub for NaiveTime { type Output = NaiveTime; From 353a29e4edece897511375ebb3b65cdf7613d1ad Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 10:54:00 +0200 Subject: [PATCH 06/57] Consistently document `Add` and `Sub` impls of `NaiveDateTime` --- src/naive/datetime/mod.rs | 115 +++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 06ddf7ebb1..af399d8a67 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1533,7 +1533,7 @@ impl Timelike for NaiveDateTime { } } -/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. +/// Add `chrono::Duration` to `NaiveDateTime`. /// /// As a part of Chrono's [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 @@ -1541,8 +1541,8 @@ impl Timelike for NaiveDateTime { /// /// # Panics /// -/// Panics if the resulting date would be out of range. Use [`NaiveDateTime::checked_add_signed`] -/// to detect that. +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. /// /// # Example /// @@ -1594,6 +1594,16 @@ impl Add for NaiveDateTime { } } +/// Add `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1605,6 +1615,16 @@ impl Add for NaiveDateTime { } } +/// Add-assign `chrono::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1612,6 +1632,16 @@ impl AddAssign for NaiveDateTime { } } +/// Add-assign `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for NaiveDateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1619,6 +1649,12 @@ impl AddAssign for NaiveDateTime { } } +/// Add `FixedOffset` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_offset` to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1628,11 +1664,15 @@ impl Add for NaiveDateTime { } } -/// An addition of months to `NaiveDateTime` clamped to valid days in resulting month. +/// Add `Months` to `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using `checked_add_months` to get an `Option` instead. /// /// # Example /// @@ -1672,15 +1712,18 @@ impl Add for NaiveDateTime { } } -/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. -/// It is the same as the addition with a negated `Duration`. +/// Subtract `chrono::Duration` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `Duration`. /// /// As a part of Chrono's [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**. /// -/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_sub_signed`] -/// to detect that. +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. /// /// # Example /// @@ -1730,6 +1773,16 @@ impl Sub for NaiveDateTime { } } +/// Subtract `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [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**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1741,6 +1794,18 @@ impl Sub for NaiveDateTime { } } +/// Subtract-assign `chrono::Duration` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1748,6 +1813,16 @@ impl SubAssign for NaiveDateTime { } } +/// Subtract-assign `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for NaiveDateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1755,6 +1830,12 @@ impl SubAssign for NaiveDateTime { } } +/// Subtract `FixedOffset` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_offset` to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -1764,11 +1845,15 @@ impl Sub for NaiveDateTime { } } -/// A subtraction of Months from `NaiveDateTime` clamped to valid days in resulting month. +/// Subtract `Months` from `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`NaiveDateTime::checked_sub_months`] for details. /// /// # Panics /// /// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_months`] to get an `Option` instead. /// /// # Example /// @@ -1844,6 +1929,12 @@ impl Sub for NaiveDateTime { } } +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_days` to get an `Option` instead. impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -1852,6 +1943,12 @@ impl Add for NaiveDateTime { } } +/// Subtract `Days` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_days` to get an `Option` instead. impl Sub for NaiveDateTime { type Output = NaiveDateTime; From 652bd7842ab5a6856f241c23260789cbf3ab6c37 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 12:28:06 +0200 Subject: [PATCH 07/57] Document `Add` and `Sub` impls of `DateTime` --- src/datetime/mod.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b05744e653..3efd303817 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1208,6 +1208,16 @@ impl hash::Hash for DateTime { } } +/// Add `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1217,6 +1227,16 @@ impl Add for DateTime { } } +/// Add `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1228,6 +1248,16 @@ impl Add for DateTime { } } +/// Add-assign `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: OldDuration) { @@ -1238,6 +1268,16 @@ impl AddAssign for DateTime { } } +/// Add-assign `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [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 +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. impl AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: Duration) { @@ -1247,6 +1287,11 @@ impl AddAssign for DateTime { } } +/// Add `FixedOffset` to the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. impl Add for DateTime { type Output = DateTime; @@ -1258,6 +1303,19 @@ impl Add for DateTime { } } +/// Add `Months` to `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_add_months`] to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1266,6 +1324,18 @@ impl Add for DateTime { } } +/// Subtract `chrono::Duration` from `DateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1275,6 +1345,16 @@ impl Sub for DateTime { } } +/// Subtract `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1286,6 +1366,18 @@ impl Sub for DateTime { } } +/// Subtract-assign `chrono::Duration` from `DateTime`. +/// +/// This is the same as the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: OldDuration) { @@ -1296,6 +1388,16 @@ impl SubAssign for DateTime { } } +/// Subtract-assign `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. impl SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: Duration) { @@ -1305,6 +1407,11 @@ impl SubAssign for DateTime { } } +/// Subtract `FixedOffset` from the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. impl Sub for DateTime { type Output = DateTime; @@ -1316,6 +1423,19 @@ impl Sub for DateTime { } } +/// Subtract `Months` from `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`DateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_sub_months`] to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; @@ -1342,6 +1462,16 @@ impl Sub<&DateTime> for DateTime { } } +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. impl Add for DateTime { type Output = DateTime; @@ -1350,6 +1480,16 @@ impl Add for DateTime { } } +/// Subtract `Days` from `DateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. impl Sub for DateTime { type Output = DateTime; From cf62e4648c5c42f0ccc17483d48cdaeaa9cb3788 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 3 May 2023 17:15:26 +0200 Subject: [PATCH 08/57] Fix panic in default impl of `TimeZone::from_local_datetime` --- src/naive/datetime/tests.rs | 24 +++++++++++++++++-- src/offset/mod.rs | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 4558bf7f42..067438ce71 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -1,7 +1,6 @@ use super::NaiveDateTime; use crate::duration::Duration as OldDuration; -use crate::NaiveDate; -use crate::{Datelike, FixedOffset, Utc}; +use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, Utc}; #[test] fn test_datetime_from_timestamp_millis() { @@ -490,3 +489,24 @@ fn test_checked_sub_offset() { assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); } + +#[test] +fn test_and_timezone_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = NaiveDateTime::MAX.and_local_timezone(offset); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, LocalResult::None); + } + let local_min = NaiveDateTime::MIN.and_local_timezone(offset); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, LocalResult::None); + } + } +} diff --git a/src/offset/mod.rs b/src/offset/mod.rs index c3154ee894..fae7da00d3 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -500,8 +500,24 @@ pub trait TimeZone: Sized + Clone { /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. #[allow(clippy::wrong_self_convention)] fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - self.offset_from_local_datetime(local) - .map(|offset| DateTime::from_naive_utc_and_offset(*local - offset.fix(), offset)) + // Return `LocalResult::None` when the offset pushes a value out of range, instead of + // panicking. + match self.offset_from_local_datetime(local) { + LocalResult::None => LocalResult::None, + LocalResult::Single(offset) => match local.checked_sub_offset(offset.fix()) { + Some(dt) => LocalResult::Single(DateTime::from_naive_utc_and_offset(dt, offset)), + None => LocalResult::None, + }, + LocalResult::Ambiguous(o1, o2) => { + match (local.checked_sub_offset(o1.fix()), local.checked_sub_offset(o2.fix())) { + (Some(d1), Some(d2)) => LocalResult::Ambiguous( + DateTime::from_naive_utc_and_offset(d1, o1), + DateTime::from_naive_utc_and_offset(d2, o2), + ), + _ => LocalResult::None, + } + } + } } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. @@ -531,6 +547,32 @@ pub trait TimeZone: Sized + Clone { mod tests { use super::*; + #[test] + fn test_fixed_offset_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = offset.from_utc_datetime(&NaiveDateTime::MAX); + assert_eq!(local_max.naive_utc(), NaiveDateTime::MAX); + let local_min = offset.from_utc_datetime(&NaiveDateTime::MIN); + assert_eq!(local_min.naive_utc(), NaiveDateTime::MIN); + + let local_max = offset.from_local_datetime(&NaiveDateTime::MAX); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, LocalResult::None); + } + let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, LocalResult::None); + } + } + } + #[test] fn test_negative_millis() { let dt = Utc.timestamp_millis_opt(-1000).unwrap(); From 00324314910fedb1192bedbcb167b0ffca673e41 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 19:07:21 +0200 Subject: [PATCH 09/57] Remove obsolete overflow check --- src/format/parsed.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index ad0d0013a3..fb9d113442 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -634,11 +634,6 @@ impl Parsed { let datetime = self.to_naive_datetime_with_offset(offset)?; 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)?; - match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), From 068b4bd6772b6e35be3756856478f261070b9885 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 20:26:05 +0200 Subject: [PATCH 10/57] Add `DateTime::to_utc` --- src/datetime/mod.rs | 8 ++++++++ src/datetime/tests.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 3efd303817..e580050bee 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -379,6 +379,14 @@ impl DateTime { self.with_timezone(&self.offset().fix()) } + /// Turn this `DateTime` into a `DateTime`, dropping the offset and associated timezone + /// information. + #[inline] + #[must_use] + pub fn to_utc(&self) -> DateTime { + DateTime { datetime: self.datetime, offset: Utc } + } + /// Adds given `Duration` to the current date and time. /// /// # Errors diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3872e14b36..03c4949a14 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1428,6 +1428,14 @@ fn test_datetime_fixed_offset() { assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); } +#[test] +fn test_datetime_to_utc() { + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 2, 22, 23, 24, 25).unwrap(); + let dt_utc: DateTime = dt.to_utc(); + assert_eq!(dt, dt_utc); +} + #[test] fn test_add_sub_months() { let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); From b72752fd41f844b4dacb0974fe7826d8bd367be4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 18 May 2023 09:13:03 +0200 Subject: [PATCH 11/57] Adjust MIN_YEAR and MAX_YEAR --- src/naive/date.rs | 27 +++++++++++++++------------ src/naive/datetime/mod.rs | 10 +++++----- src/naive/internals.rs | 11 +++++++++-- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 9677b0c439..6a99a2438c 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1457,10 +1457,10 @@ impl NaiveDate { self.of().weekday() } - /// The minimum possible `NaiveDate` (January 1, 262145 BCE). - pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; - /// The maximum possible `NaiveDate` (December 31, 262143 CE). - pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). + pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; + /// The maximum possible `NaiveDate` (December 31, 262142 CE). + pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; } impl Datelike for NaiveDate { @@ -2277,8 +2277,8 @@ where to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), Some(r#""-0001-12-31""#.into()) ); - assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262144-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262143-12-31""#.into())); + assert_eq!(to_string(&NaiveDate::MIN).ok(), Some(r#""-262143-01-01""#.into())); + assert_eq!(to_string(&NaiveDate::MAX).ok(), Some(r#""+262142-12-31""#.into())); } #[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] @@ -2301,8 +2301,8 @@ where from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) ); - assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(NaiveDate::MIN)); - assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(NaiveDate::MAX)); + assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); // bad formats assert!(from_str(r#""""#).is_err()); @@ -3155,9 +3155,12 @@ mod tests { #[test] fn test_day_iterator_limit() { - assert_eq!(NaiveDate::from_ymd_opt(262143, 12, 29).unwrap().iter_days().take(4).count(), 2); assert_eq!( - NaiveDate::from_ymd_opt(-262144, 1, 3).unwrap().iter_days().rev().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), + 2 + ); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), 2 ); } @@ -3165,11 +3168,11 @@ mod tests { #[test] fn test_week_iterator_limit() { assert_eq!( - NaiveDate::from_ymd_opt(262143, 12, 12).unwrap().iter_weeks().take(4).count(), + NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2 ); assert_eq!( - NaiveDate::from_ymd_opt(-262144, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), 2 ); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index af399d8a67..535be49f89 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -2123,11 +2123,11 @@ where ); assert_eq!( to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), - Some(r#""-262144-01-01T00:00:00""#.into()) + Some(r#""-262143-01-01T00:00:00""#.into()) ); assert_eq!( to_string(&NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()).ok(), - Some(r#""+262143-12-31T23:59:60.999999999""#.into()) + Some(r#""+262142-12-31T23:59:60.999999999""#.into()) ); } @@ -2166,15 +2166,15 @@ where Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap().and_hms_nano_opt(23, 59, 59, 7).unwrap()) ); assert_eq!( - from_str(r#""-262144-01-01T00:00:00""#).ok(), + from_str(r#""-262143-01-01T00:00:00""#).ok(), Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) ); assert_eq!( - from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), + from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); assert_eq!( - from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored + from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) ); diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 910419f387..c6d7536a0c 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -21,8 +21,15 @@ use core::fmt; /// The internal date representation: `year << 13 | Of` pub(super) type DateImpl = i32; -pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13; -pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: DateImpl = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: DateImpl = (i32::MIN >> 13) + 1; /// The year flags (aka the dominical letter). /// From 39773653e661a6537044d15de0d1ebdb1e1ba38c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 23 Sep 2023 14:24:48 +0200 Subject: [PATCH 12/57] Add `NaiveDate::{BEFORE_MIN, AFTER_MAX}` --- src/naive/date.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/naive/date.rs b/src/naive/date.rs index 6a99a2438c..1c57b89d62 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1461,6 +1461,13 @@ impl NaiveDate { pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). pub const MAX: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o16 /*G*/ }; + + /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). + pub(crate) const BEFORE_MIN: NaiveDate = + NaiveDate { ymdf: ((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /*FE*/ }; + /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). + pub(crate) const AFTER_MAX: NaiveDate = + NaiveDate { ymdf: ((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /*F*/ }; } impl Datelike for NaiveDate { @@ -2444,6 +2451,7 @@ mod serde { mod tests { use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; use crate::duration::Duration; + use crate::naive::internals::YearFlags; use crate::{Datelike, Weekday}; // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, @@ -2473,6 +2481,14 @@ mod tests { "The entire `NaiveDate` range somehow exceeds 2^{} seconds", MAX_BITS ); + + const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; + assert_eq!(BEFORE_MIN.of().flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); + + const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; + assert_eq!(AFTER_MAX.of().flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); } #[test] From 6f94e45c0e478c7633f275571478ed1cb4ca2449 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 12:20:35 +0200 Subject: [PATCH 13/57] Tests and nicer panic message for `DateTime::naive_local` --- src/datetime/mod.rs | 4 +++- src/datetime/tests.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index e580050bee..83b1dced29 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -513,7 +513,9 @@ impl DateTime { #[inline] #[must_use] pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() + self.datetime + .checked_add_offset(self.offset.fix()) + .expect("Local time out of range for `NaiveDateTime`") } /// Retrieve the elapsed years from now to the given [`DateTime`]. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 03c4949a14..5d8876a120 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1331,6 +1331,20 @@ fn test_datetime_sub_assign() { assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); } +#[test] +#[should_panic] +fn test_local_beyond_min_datetime() { + let min = FixedOffset::west_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MIN); + let _ = min.naive_local(); +} + +#[test] +#[should_panic] +fn test_local_beyond_max_datetime() { + let max = FixedOffset::east_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MAX); + let _ = max.naive_local(); +} + #[test] #[cfg(feature = "clock")] fn test_datetime_sub_assign_local() { From 4ca5a5292f2b5d3ec3e3fa394b186d30724e3fb4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 4 May 2023 17:03:30 +0200 Subject: [PATCH 14/57] Add `overflowing_naive_local` and `NaiveDateTime::overflowing_add_offset` --- src/datetime/mod.rs | 11 +++++++++++ src/naive/datetime/mod.rs | 16 ++++++++++++++++ src/naive/datetime/tests.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 83b1dced29..58cc7af288 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -518,6 +518,17 @@ impl DateTime { .expect("Local time out of range for `NaiveDateTime`") } + /// Returns the naive local datetime. + /// + /// This makes use of the buffer space outside of the representable range of values of + /// `NaiveDateTime`. The result can be used as intermediate value, but should never be exposed + /// outside chrono. + #[inline] + #[must_use] + pub(crate) fn overflowing_naive_local(&self) -> NaiveDateTime { + self.datetime.overflowing_add_offset(self.offset.fix()) + } + /// Retrieve the elapsed years from now to the given [`DateTime`]. /// /// # Errors diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 535be49f89..64f52d8d19 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -738,6 +738,22 @@ impl NaiveDateTime { Some(NaiveDateTime { date, time }) } + /// Adds given `FixedOffset` to the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + pub(crate) fn overflowing_add_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + 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), diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 067438ce71..97604db74f 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -490,6 +490,32 @@ fn test_checked_sub_offset() { assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); } +#[test] +fn test_overflowing_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() + }; + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN); +} + #[test] fn test_and_timezone_min_max_dates() { for offset_hour in -23..=23 { From e6580db09516d131822dcadbf9646bc23ac40b33 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 17:34:29 +0200 Subject: [PATCH 15/57] Use `overflowing_naive_local` in methods that don't return `DateTime` --- src/datetime/mod.rs | 40 ++++++++++++++--------------- src/datetime/tests.rs | 60 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 58cc7af288..4e1befc978 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -22,7 +22,7 @@ use crate::format::{ StrftimeItems, TOO_LONG, }; #[cfg(feature = "alloc")] -use crate::format::{write_rfc3339, DelayedFormat}; +use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -561,7 +561,7 @@ impl DateTime { #[must_use] pub fn to_rfc2822(&self) -> String { let mut result = String::with_capacity(32); - crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) + write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) .expect("writing rfc2822 datetime to string should never fail"); result } @@ -572,7 +572,7 @@ impl DateTime { pub fn to_rfc3339(&self) -> String { // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. let mut result = String::with_capacity(32); - let naive = self.naive_local(); + let naive = self.overflowing_naive_local(); let offset = self.offset.fix(); write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) .expect("writing rfc3339 datetime to string should never fail"); @@ -888,7 +888,7 @@ where I: Iterator + Clone, B: Borrow>, { - let local = self.naive_local(); + let local = self.overflowing_naive_local(); DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) } @@ -924,7 +924,7 @@ where I: Iterator + Clone, B: Borrow>, { - let local = self.naive_local(); + let local = self.overflowing_naive_local(); DelayedFormat::new_with_offset_and_locale( Some(local.date()), Some(local.time()), @@ -954,39 +954,39 @@ where impl Datelike for DateTime { #[inline] fn year(&self) -> i32 { - self.naive_local().year() + self.overflowing_naive_local().year() } #[inline] fn month(&self) -> u32 { - self.naive_local().month() + self.overflowing_naive_local().month() } #[inline] fn month0(&self) -> u32 { - self.naive_local().month0() + self.overflowing_naive_local().month0() } #[inline] fn day(&self) -> u32 { - self.naive_local().day() + self.overflowing_naive_local().day() } #[inline] fn day0(&self) -> u32 { - self.naive_local().day0() + self.overflowing_naive_local().day0() } #[inline] fn ordinal(&self) -> u32 { - self.naive_local().ordinal() + self.overflowing_naive_local().ordinal() } #[inline] fn ordinal0(&self) -> u32 { - self.naive_local().ordinal0() + self.overflowing_naive_local().ordinal0() } #[inline] fn weekday(&self) -> Weekday { - self.naive_local().weekday() + self.overflowing_naive_local().weekday() } #[inline] fn iso_week(&self) -> IsoWeek { - self.naive_local().iso_week() + self.overflowing_naive_local().iso_week() } #[inline] @@ -1105,19 +1105,19 @@ impl Datelike for DateTime { impl Timelike for DateTime { #[inline] fn hour(&self) -> u32 { - self.naive_local().hour() + self.overflowing_naive_local().hour() } #[inline] fn minute(&self) -> u32 { - self.naive_local().minute() + self.overflowing_naive_local().minute() } #[inline] fn second(&self) -> u32 { - self.naive_local().second() + self.overflowing_naive_local().second() } #[inline] fn nanosecond(&self) -> u32 { - self.naive_local().nanosecond() + self.overflowing_naive_local().nanosecond() } /// Makes a new `DateTime` with the hour number changed. @@ -1521,7 +1521,7 @@ impl Sub for DateTime { impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.naive_local().fmt(f)?; + self.overflowing_naive_local().fmt(f)?; self.offset.fmt(f) } } @@ -1531,7 +1531,7 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.naive_local().fmt(f)?; + self.overflowing_naive_local().fmt(f)?; f.write_char(' ')?; self.offset.fmt(f) } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 5d8876a120..29de0dc93f 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -4,7 +4,7 @@ use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; #[cfg(feature = "clock")] use crate::offset::{Local, Offset}; -use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike}; +use crate::{Datelike, Days, LocalResult, Months, NaiveDateTime, Timelike, Weekday}; #[derive(Clone)] struct DstTester; @@ -1331,6 +1331,64 @@ fn test_datetime_sub_assign() { assert_eq!(datetime_sub, datetime - OldDuration::minutes(90)); } +#[test] +fn test_min_max_getters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_min.to_rfc2822(), ""); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!( + beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), + "-262144-12-31T22:00:00-02:00" + ); + assert_eq!(beyond_min.year(), -262144); + assert_eq!(beyond_min.month(), 12); + assert_eq!(beyond_min.month0(), 11); + assert_eq!(beyond_min.day(), 31); + assert_eq!(beyond_min.day0(), 30); + assert_eq!(beyond_min.ordinal(), 366); + assert_eq!(beyond_min.ordinal0(), 365); + assert_eq!(beyond_min.weekday(), Weekday::Wed); + assert_eq!(beyond_min.iso_week().year(), -262143); + assert_eq!(beyond_min.iso_week().week(), 1); + assert_eq!(beyond_min.hour(), 22); + assert_eq!(beyond_min.minute(), 0); + assert_eq!(beyond_min.second(), 0); + assert_eq!(beyond_min.nanosecond(), 0); + + assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_max.to_rfc2822(), ""); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!( + beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), + "+262143-01-01T01:59:59.999999999+02:00" + ); + assert_eq!(beyond_max.year(), 262143); + assert_eq!(beyond_max.month(), 1); + assert_eq!(beyond_max.month0(), 0); + assert_eq!(beyond_max.day(), 1); + assert_eq!(beyond_max.day0(), 0); + assert_eq!(beyond_max.ordinal(), 1); + assert_eq!(beyond_max.ordinal0(), 0); + assert_eq!(beyond_max.weekday(), Weekday::Tue); + assert_eq!(beyond_max.iso_week().year(), 262143); + assert_eq!(beyond_max.iso_week().week(), 1); + assert_eq!(beyond_max.hour(), 1); + assert_eq!(beyond_max.minute(), 59); + assert_eq!(beyond_max.second(), 59); + assert_eq!(beyond_max.nanosecond(), 999_999_999); +} + #[test] #[should_panic] fn test_local_beyond_min_datetime() { From e28f2afa700032108abf5fea26f57a00b4286ef6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 18:10:01 +0200 Subject: [PATCH 16/57] Use `overflowing_naive_local` in `map_local` This fixes out-of-range panics in all the `with_*` methods that use `map_local`. --- src/datetime/mod.rs | 4 +++- src/datetime/tests.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 4e1befc978..5c9e888b6e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -750,7 +750,9 @@ fn map_local(dt: &DateTime, mut f: F) -> Option Option, { - f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + f(dt.overflowing_naive_local()) + .and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC && dt <= &DateTime::::MAX_UTC) } impl DateTime { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 29de0dc93f..9e41a51096 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1389,6 +1389,54 @@ fn test_min_max_getters() { assert_eq!(beyond_max.nanosecond(), 999_999_999); } +#[test] +fn test_min_max_setters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(beyond_min.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_min.with_month(beyond_min.month()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(3), None); + assert_eq!(beyond_min.with_month0(beyond_min.month0()), Some(beyond_min)); + assert_eq!(beyond_min.with_month0(3), None); + assert_eq!(beyond_min.with_day(beyond_min.day()), Some(beyond_min)); + assert_eq!(beyond_min.with_day(15), None); + assert_eq!(beyond_min.with_day0(beyond_min.day0()), Some(beyond_min)); + assert_eq!(beyond_min.with_day0(15), None); + assert_eq!(beyond_min.with_ordinal(beyond_min.ordinal()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal(200), None); + assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal0(200), None); + assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); + assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(OldDuration::hours(1))); + assert_eq!(beyond_min.with_hour(5), None); + assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); + assert_eq!(beyond_min.with_second(0), Some(beyond_min)); + assert_eq!(beyond_min.with_nanosecond(0), Some(beyond_min)); + + assert_eq!(beyond_max.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_max.with_month(beyond_max.month()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(3), None); + assert_eq!(beyond_max.with_month0(beyond_max.month0()), Some(beyond_max)); + assert_eq!(beyond_max.with_month0(3), None); + assert_eq!(beyond_max.with_day(beyond_max.day()), Some(beyond_max)); + assert_eq!(beyond_max.with_day(15), None); + assert_eq!(beyond_max.with_day0(beyond_max.day0()), Some(beyond_max)); + assert_eq!(beyond_max.with_day0(15), None); + assert_eq!(beyond_max.with_ordinal(beyond_max.ordinal()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal(200), None); + assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal0(200), None); + assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); + assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(OldDuration::hours(1))); + assert_eq!(beyond_max.with_hour(5), None); + assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); + assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); + assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); +} + #[test] #[should_panic] fn test_local_beyond_min_datetime() { From 22846c9df1e79417eed477cfc5831c5d483c4eec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 26 Sep 2023 16:02:04 +0200 Subject: [PATCH 17/57] Inline `write_rfc2822_inner`, don't localize --- src/format/formatting.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 8cb83c7521..f11c680e01 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -395,7 +395,7 @@ fn format_inner( // same as `%a, %d %b %Y %H:%M:%S %z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(w, *d, *t, off, locale)) + Some(write_rfc2822(w, crate::NaiveDateTime::new(*d, *t), off)) } else { None } @@ -576,45 +576,35 @@ pub(crate) fn write_rfc2822( dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result { - write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) -} - -#[cfg(feature = "alloc")] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -fn write_rfc2822_inner( - w: &mut impl Write, - d: NaiveDate, - t: NaiveTime, - off: FixedOffset, - locale: Locale, -) -> fmt::Result { - let year = d.year(); + let year = dt.year(); // RFC2822 is only defined on years 0 through 9999 if !(0..=9999).contains(&year) { return Err(fmt::Error); } - w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + let english = default_locale(); + + w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?; w.write_str(", ")?; - let day = d.day(); + let day = dt.day(); if day < 10 { w.write_char((b'0' + day as u8) as char)?; } else { write_hundreds(w, day as u8)?; } w.write_char(' ')?; - w.write_str(short_months(locale)[d.month0() as usize])?; + w.write_str(short_months(english)[dt.month0() as usize])?; w.write_char(' ')?; write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; w.write_char(' ')?; - let (hour, min, sec) = t.hms(); + let (hour, min, sec) = dt.time().hms(); write_hundreds(w, hour as u8)?; w.write_char(':')?; write_hundreds(w, min as u8)?; w.write_char(':')?; - let sec = sec + t.nanosecond() / 1_000_000_000; + let sec = sec + dt.nanosecond() / 1_000_000_000; write_hundreds(w, sec as u8)?; w.write_char(' ')?; OffsetFormat { From 295e9c83211aec160800298591039b107a356b4a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:06:55 +0200 Subject: [PATCH 18/57] Tweaks to visibility in main documentation --- src/lib.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7841afb42c..3488b9bea2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -477,6 +477,7 @@ extern crate alloc; mod duration; pub use duration::Duration; #[cfg(feature = "std")] +#[doc(no_inline)] pub use duration::OutOfRangeError; use core::fmt; @@ -508,13 +509,18 @@ pub mod prelude { mod date; #[allow(deprecated)] -pub use date::{Date, MAX_DATE, MIN_DATE}; +pub use date::Date; +#[doc(no_inline)] +#[allow(deprecated)] +pub use date::{MAX_DATE, MIN_DATE}; mod datetime; #[cfg(feature = "rustc-serialize")] pub use datetime::rustc_serialize::TsSeconds; +pub use datetime::DateTime; #[allow(deprecated)] -pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; +#[doc(no_inline)] +pub use datetime::{SecondsFormat, MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. @@ -523,24 +529,30 @@ pub use format::Locale; pub use format::{ParseError, ParseResult}; pub mod naive; -#[doc(no_inline)] -pub use naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; +#[doc(inline)] +pub use naive::{Days, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] -#[doc(no_inline)] +#[doc(inline)] pub use offset::Local; -#[doc(no_inline)] -pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; +pub use offset::LocalResult; +#[doc(inline)] +pub use offset::{FixedOffset, Offset, TimeZone, Utc}; mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; mod weekday; -pub use weekday::{ParseWeekdayError, Weekday}; +#[doc(no_inline)] +pub use weekday::ParseWeekdayError; +pub use weekday::Weekday; mod month; -pub use month::{Month, Months, ParseMonthError}; +#[doc(no_inline)] +pub use month::ParseMonthError; +pub use month::{Month, Months}; mod traits; pub use traits::{Datelike, Timelike}; From 017b621033b6aae06349ae36021b74f91b892391 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:20 +0200 Subject: [PATCH 19/57] Remove useless `#[doc(no_inline)]` from `prelude` module --- src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3488b9bea2..2af2aef8d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -484,26 +484,17 @@ use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { - #[doc(no_inline)] #[allow(deprecated)] pub use crate::Date; #[cfg(feature = "clock")] - #[doc(no_inline)] pub use crate::Local; #[cfg(all(feature = "unstable-locales", feature = "alloc"))] - #[doc(no_inline)] pub use crate::Locale; - #[doc(no_inline)] pub use crate::SubsecRound; - #[doc(no_inline)] pub use crate::{DateTime, SecondsFormat}; - #[doc(no_inline)] pub use crate::{Datelike, Month, Timelike, Weekday}; - #[doc(no_inline)] pub use crate::{FixedOffset, Utc}; - #[doc(no_inline)] pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; - #[doc(no_inline)] pub use crate::{Offset, TimeZone}; } From 5da23657ccfaecde6af998e728ee51c909096c54 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:32 +0200 Subject: [PATCH 20/57] Hide private macros --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2af2aef8d9..79c098579d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -614,6 +614,7 @@ impl std::error::Error for OutOfRange {} /// Workaround because `?` is not (yet) available in const context. #[macro_export] +#[doc(hidden)] macro_rules! try_opt { ($e:expr) => { match $e { @@ -625,6 +626,7 @@ macro_rules! try_opt { /// Workaround because `.expect()` is not (yet) available in const context. #[macro_export] +#[doc(hidden)] macro_rules! expect { ($e:expr, $m:literal) => { match $e { From 819dea0480dd99d682b11e4f7ac5e2a588d2e202 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:07:08 +0200 Subject: [PATCH 21/57] `Local` does not depend on the `time` crate --- src/offset/local/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index ffd2844d55..e4f4e1631f 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -88,7 +88,7 @@ mod inner { #[cfg(unix)] mod tz_info; -/// The local timescale. This is implemented via the standard `time` crate. +/// The local timescale. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on the Local struct is the preferred way to construct `DateTime` From 5237de6e0a1996ed65d74458b581a63aaeb0623e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 25 Sep 2023 13:44:37 +0200 Subject: [PATCH 22/57] Make `round` module public (so we don't need to show it in the main documentation) --- src/lib.rs | 2 +- src/round.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 79c098579d..f7638bc6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -532,7 +532,7 @@ pub use offset::LocalResult; #[doc(inline)] pub use offset::{FixedOffset, Offset, TimeZone, Utc}; -mod round; +pub mod round; pub use round::{DurationRound, RoundingError, SubsecRound}; mod weekday; diff --git a/src/round.rs b/src/round.rs index 795ffb04cd..4de1de60c8 100644 --- a/src/round.rs +++ b/src/round.rs @@ -1,6 +1,8 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. +//! Functionality for rounding or truncating a `DateTime` by a `Duration`. + use crate::datetime::DateTime; use crate::duration::Duration; use crate::NaiveDateTime; From b7857baed26e8011e04421a49173b858759a1665 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:06:55 +0200 Subject: [PATCH 23/57] Move `SecondsFormat` from `datetime` to `format::formatting` --- src/datetime/mod.rs | 34 +-------------------------------- src/datetime/rustc_serialize.rs | 3 ++- src/datetime/serde.rs | 4 ++-- src/format/formatting.rs | 34 +++++++++++++++++++++++++++++++-- src/format/mod.rs | 1 + src/lib.rs | 4 ++-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5c9e888b6e..24a344d9ab 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -22,7 +22,7 @@ use crate::format::{ StrftimeItems, TOO_LONG, }; #[cfg(feature = "alloc")] -use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat}; +use crate::format::{write_rfc2822, write_rfc3339, DelayedFormat, SecondsFormat}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] use crate::offset::Local; @@ -44,38 +44,6 @@ pub(super) mod serde; #[cfg(test)] mod tests; -/// Specific formatting options for seconds. This may be extended in the -/// future, so exhaustive matching in external code is not recommended. -/// -/// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[allow(clippy::manual_non_exhaustive)] -pub enum SecondsFormat { - /// Format whole seconds only, with no decimal point nor subseconds. - Secs, - - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). - Millis, - - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). - Micros, - - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). - Nanos, - - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). - AutoSi, - - // Do not match against this. - #[doc(hidden)] - __NonExhaustive, -} - /// ISO 8601 combined date and time with time zone. /// /// There are some constructors implemented here (the `from_*` methods), but diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 51ef5b7230..f704b2f97b 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,4 +1,5 @@ -use super::{DateTime, SecondsFormat}; +use super::DateTime; +use crate::format::SecondsFormat; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 85655c99a6..dcdb36dd0b 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1,8 +1,8 @@ use core::fmt; use serde::{de, ser}; -use super::{DateTime, SecondsFormat}; -use crate::format::write_rfc3339; +use super::DateTime; +use crate::format::{write_rfc3339, SecondsFormat}; use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; diff --git a/src/format/formatting.rs b/src/format/formatting.rs index f11c680e01..c0986732a5 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -11,8 +11,6 @@ use core::borrow::Borrow; use core::fmt::Display; use core::fmt::{self, Write}; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] -use crate::datetime::SecondsFormat; #[cfg(feature = "alloc")] use crate::offset::Offset; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] @@ -504,6 +502,38 @@ impl OffsetFormat { } } +/// Specific formatting options for seconds. This may be extended in the +/// future, so exhaustive matching in external code is not recommended. +/// +/// See the `TimeZone::to_rfc3339_opts` function for usage. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[allow(clippy::manual_non_exhaustive)] +pub enum SecondsFormat { + /// Format whole seconds only, with no decimal point nor subseconds. + Secs, + + /// Use fixed 3 subsecond digits. This corresponds to + /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). + Millis, + + /// Use fixed 6 subsecond digits. This corresponds to + /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). + Micros, + + /// Use fixed 9 subsecond digits. This corresponds to + /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). + Nanos, + + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to + /// display all available non-zero sub-second digits. This corresponds to + /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). + AutoSi, + + // Do not match against this. + #[doc(hidden)] + __NonExhaustive, +} + /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] diff --git a/src/format/mod.rs b/src/format/mod.rs index df75b4ffbc..fda4d425b6 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -58,6 +58,7 @@ pub(crate) use formatting::write_hundreds; pub(crate) use formatting::write_rfc2822; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) use formatting::write_rfc3339; +pub use formatting::SecondsFormat; #[cfg(feature = "alloc")] #[allow(deprecated)] pub use formatting::{format, format_item, DelayedFormat}; diff --git a/src/lib.rs b/src/lib.rs index f7638bc6ac..da8e00d17a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -511,13 +511,13 @@ pub use datetime::rustc_serialize::TsSeconds; pub use datetime::DateTime; #[allow(deprecated)] #[doc(no_inline)] -pub use datetime::{SecondsFormat, MAX_DATETIME, MIN_DATETIME}; +pub use datetime::{MAX_DATETIME, MIN_DATETIME}; pub mod format; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use format::Locale; -pub use format::{ParseError, ParseResult}; +pub use format::{ParseError, ParseResult, SecondsFormat}; pub mod naive; #[doc(inline)] From 604b92a2415177e7513a44fe712b61866758a69b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 20:59:13 +0200 Subject: [PATCH 24/57] Convert manual documentation links --- src/format/formatting.rs | 14 +++++--------- src/naive/date.rs | 26 ++++++++++++-------------- src/naive/datetime/mod.rs | 20 +++++++++----------- src/naive/time/mod.rs | 12 ++++++------ src/offset/mod.rs | 3 +-- 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index c0986732a5..9b055289ce 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -512,21 +512,17 @@ pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). + /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3]. Millis, - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). + /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6]. Micros, - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). + /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9]. Nanos, - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available + /// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond]. AutoSi, // Do not match against this. diff --git a/src/naive/date.rs b/src/naive/date.rs index 1c57b89d62..379e667a56 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -100,8 +100,7 @@ impl NaiveWeek { } /// Returns a [`RangeInclusive`] representing the whole week bounded by - /// [first_day](./struct.NaiveWeek.html#method.first_day) and - /// [last_day](./struct.NaiveWeek.html#method.last_day) functions. + /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions. /// /// # Panics /// @@ -160,12 +159,12 @@ impl Days { /// /// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). /// If you need a typical BCE/BC and CE/AD notation for year numbers, -/// use the [`Datelike::year_ce`](../trait.Datelike.html#method.year_ce) method. +/// use the [`Datelike::year_ce`] method. /// /// # Week Date /// /// The ISO 8601 **week date** is a triple of year number, week number -/// and [day of the week](../enum.Weekday.html) with the following rules: +/// and [day of the week](Weekday) with the following rules: /// /// * A week consists of Monday through Sunday, and is always numbered within some year. /// The week number ranges from 1 to 52 or 53 depending on the year. @@ -176,10 +175,9 @@ impl Days { /// * The year number in the week date may *not* correspond to the actual Gregorian year. /// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. /// -/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), -/// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and -/// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods -/// can be used to get the corresponding week date. +/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), but +/// [`Datelike::iso_week`] and [`Datelike::weekday`] methods can be used to get the corresponding +/// week date. /// /// # Ordinal Date /// @@ -542,7 +540,7 @@ impl NaiveDate { } /// Parses a string with the specified format string and returns a new `NaiveDate`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -591,7 +589,7 @@ impl NaiveDate { /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with /// the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -1280,7 +1278,7 @@ impl NaiveDate { } /// Formats the date with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -2139,7 +2137,7 @@ impl DoubleEndedIterator for NaiveDateWeeksIterator { impl FusedIterator for NaiveDateWeeksIterator {} /// The `Debug` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -2182,7 +2180,7 @@ impl fmt::Debug for NaiveDate { } /// The `Display` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -2210,7 +2208,7 @@ impl fmt::Display for NaiveDate { } /// Parsing a `str` into a `NaiveDate` uses the same format, -/// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`. +/// [`%Y-%m-%d`](crate::format::strftime), as in `Debug` and `Display`. /// /// # Example /// diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 64f52d8d19..e6558de2e4 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -53,7 +53,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// /// # Example /// -/// `NaiveDateTime` is commonly created from [`NaiveDate`](./struct.NaiveDate.html). +/// `NaiveDateTime` is commonly created from [`NaiveDate`]. /// /// ``` /// use chrono::{NaiveDate, NaiveDateTime}; @@ -62,8 +62,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// # let _ = dt; /// ``` /// -/// You can use typical [date-like](../trait.Datelike.html) and -/// [time-like](../trait.Timelike.html) methods, +/// You can use typical [date-like](Datelike) and [time-like](Timelike) methods, /// provided that relevant traits are in the scope. /// /// ``` @@ -113,8 +112,7 @@ impl NaiveDateTime { /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// For a non-naive version of this function see - /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). + /// For a non-naive version of this function see [`TimeZone::timestamp`]. /// /// The nanosecond part can exceed 1,000,000,000 in order to represent a /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. @@ -244,7 +242,7 @@ impl NaiveDateTime { } /// Parses a string with the specified format string and returns a new `NaiveDateTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -322,7 +320,7 @@ impl NaiveDateTime { /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a /// slice with the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -958,7 +956,7 @@ impl NaiveDateTime { } /// Formats the combined date and time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -1087,7 +1085,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 11. /// - /// See also the [`NaiveDate::month0`](./struct.NaiveDate.html#method.month0) method. + /// See also the [`NaiveDate::month0`] method. /// /// # Example /// @@ -1125,7 +1123,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 30. (The last day of month differs by months.) /// - /// See also the [`NaiveDate::day0`](./struct.NaiveDate.html#method.day0) method. + /// See also the [`NaiveDate::day0`] method. /// /// # Example /// @@ -1163,7 +1161,7 @@ impl Datelike for NaiveDateTime { /// /// The return value ranges from 0 to 365. (The last day of year differs by years.) /// - /// See also the [`NaiveDate::ordinal0`](./struct.NaiveDate.html#method.ordinal0) method. + /// See also the [`NaiveDate::ordinal0`] method. /// /// # Example /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 8418ffc29c..4882bb69b0 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -474,7 +474,7 @@ impl NaiveTime { } /// Parses a string with the specified format string and returns a new `NaiveTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// # Example @@ -542,7 +542,7 @@ impl NaiveTime { /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with /// the remaining portion of the string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// Similar to [`parse_from_str`](#method.parse_from_str). @@ -819,7 +819,7 @@ impl NaiveTime { } /// Formats the time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) + /// See the [`format::strftime` module](crate::format::strftime) /// on the supported escape sequences. /// /// This returns a `DelayedFormat`, @@ -1373,7 +1373,7 @@ impl Sub for NaiveTime { } /// The `Debug` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -1429,7 +1429,7 @@ impl fmt::Debug for NaiveTime { } /// The `Display` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). /// /// The string printed can be readily parsed via the `parse` method on `str`. /// @@ -1463,7 +1463,7 @@ impl fmt::Display for NaiveTime { } /// Parsing a `str` into a `NaiveTime` uses the same format, -/// [`%H:%M:%S%.f`](../format/strftime/index.html), as in `Debug` and `Display`. +/// [`%H:%M:%S%.f`](crate::format::strftime), as in `Debug` and `Display`. /// /// # Example /// diff --git a/src/offset/mod.rs b/src/offset/mod.rs index fae7da00d3..99b64b2760 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -211,8 +211,7 @@ pub trait Offset: Sized + Clone + fmt::Debug { /// The time zone. /// -/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and -/// [`DateTime`](../struct.DateTime.html) types. +/// The methods here are the primary constructors for [`Date`] and [`DateTime`] types. pub trait TimeZone: Sized + Clone { /// An associated offset type. /// This type is used to store the actual offset in date and time types. From bb4e9b03583858da8031b83fa535cbaec0055c58 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 28 Sep 2023 07:16:06 +0200 Subject: [PATCH 25/57] Remove unnecessary feature gate --- src/datetime/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 9e41a51096..42b014983d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1341,9 +1341,9 @@ fn test_min_max_getters() { assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_min.to_rfc2822(), ""); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!( beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), "-262144-12-31T22:00:00-02:00" @@ -1366,9 +1366,9 @@ fn test_min_max_getters() { assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); // RFC 2822 doesn't support years with more than 4 digits. // assert_eq!(beyond_max.to_rfc2822(), ""); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] assert_eq!( beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), "+262143-01-01T01:59:59.999999999+02:00" From 4bbfa9ffe16c95044977b506d967244bb01511ef Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 27 Sep 2023 12:50:40 +0200 Subject: [PATCH 26/57] Fix quotes in docs.rs feature list --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5f2c8cbf3..7d82ba3eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ bincode = { version = "1.3.0" } wasm-bindgen-test = "0.3" [package.metadata.docs.rs] -features = ["arbitrary, rkyv, serde, unstable-locales"] +features = ["arbitrary", "rkyv", "serde", "unstable-locales"] rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] From 6d3a77dc5a704a84f8332096b30244b185e01c1a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Jun 2023 09:43:36 +0200 Subject: [PATCH 27/57] Derive `Default` for `Duration`. --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 570c6ce5f3..7673ede27a 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -50,7 +50,7 @@ macro_rules! try_opt { /// ISO 8601 time duration with nanosecond precision. /// /// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", From 8018c326844ef140a56169ca4d0b92f4e719f601 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 08:49:56 +0200 Subject: [PATCH 28/57] Rename `Duration::nanos_mod_sec` to `subsec_nanos`, make public --- src/duration.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index 7673ede27a..b46f1705b6 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -184,9 +184,9 @@ impl Duration { } /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of + /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. - const fn nanos_mod_sec(&self) -> i32 { + pub const fn subsec_nanos(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { @@ -199,7 +199,7 @@ impl Duration { // A proper Duration will not overflow, because MIN and MAX are defined // such that the range is exactly i64 milliseconds. let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; secs_part + nanos_part as i64 } @@ -207,7 +207,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub const fn num_microseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; + let nanos_part = self.subsec_nanos() / NANOS_PER_MICRO; secs_part.checked_add(nanos_part as i64) } @@ -215,7 +215,7 @@ impl Duration { /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub const fn num_nanoseconds(&self) -> Option { let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); - let nanos_part = self.nanos_mod_sec(); + let nanos_part = self.subsec_nanos(); secs_part.checked_add(nanos_part as i64) } From a2820c4f7db20a77d69bdb62d822394c52db0aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 22 Jan 2023 19:59:13 +0100 Subject: [PATCH 29/57] Add Duration::try_* builders --- src/duration.rs | 56 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index b46f1705b6..c7958f3b36 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -80,8 +80,15 @@ impl Duration { #[inline] #[must_use] pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) + Duration::try_weeks(weeks).expect("Duration::weeks out of bounds") + } + + /// Makes a new `Duration` with given number of weeks. + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_weeks(weeks: i64) -> Option { + weeks.checked_mul(SECS_PER_WEEK).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of days. @@ -90,8 +97,15 @@ impl Duration { #[inline] #[must_use] pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) + Duration::try_days(days).expect("Duration::days out of bounds") + } + + /// Makes a new `Duration` with given number of days. + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_days(days: i64) -> Option { + days.checked_mul(SECS_PER_DAY).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of hours. @@ -100,8 +114,15 @@ impl Duration { #[inline] #[must_use] pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) + Duration::try_hours(hours).expect("Duration::hours ouf of bounds") + } + + /// Makes a new `Duration` with given number of hours. + /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_hours(hours: i64) -> Option { + hours.checked_mul(SECS_PER_HOUR).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of minutes. @@ -110,8 +131,15 @@ impl Duration { #[inline] #[must_use] pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) + Duration::try_minutes(minutes).expect("Duration::minutes out of bounds") + } + + /// Makes a new `Duration` with given number of minutes. + /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Returns `None` when the duration is out of bounds. + #[inline] + pub fn try_minutes(minutes: i64) -> Option { + minutes.checked_mul(SECS_PER_MINUTE).and_then(Duration::try_seconds) } /// Makes a new `Duration` with given number of seconds. @@ -120,11 +148,19 @@ impl Duration { #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { + Duration::try_seconds(seconds).expect("Duration::seconds out of bounds") + } + + /// Makes a new `Duration` with given number of seconds. + /// Returns `None` when the duration is more than `i64::MAX` milliseconds + /// or less than `i64::MIN` milliseconds. + #[inline] + pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); + return None; } - d + Some(d) } /// Makes a new `Duration` with given number of milliseconds. From bd6573aebec9cb337a1788cea9c65e441f5d7c36 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:19:38 +0200 Subject: [PATCH 30/57] Use `checked_add` and `checked_sub` in `Add` and `Sub` impls --- src/duration.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index c7958f3b36..add0333872 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -372,13 +372,7 @@ impl Add for Duration { type Output = Duration; fn add(self, rhs: Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs, nanos } + self.checked_add(&rhs).expect("`Duration + Duration` overflowed") } } @@ -386,13 +380,7 @@ impl Sub for Duration { type Output = Duration; fn sub(self, rhs: Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs, nanos } + self.checked_sub(&rhs).expect("`Duration - Duration` overflowed") } } From c8e3eb4ce1130dffa7eb35a2f7472d75de86a648 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 8 Jun 2023 09:41:37 +0200 Subject: [PATCH 31/57] Implement `AddAssign` and `SubAssign` --- src/duration.rs | 21 ++++++++++++++++++++- src/naive/time/mod.rs | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index add0333872..fb64591b80 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -10,7 +10,7 @@ //! Temporal quantification -use core::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use core::time::Duration as StdDuration; use core::{fmt, i64}; #[cfg(feature = "std")] @@ -384,6 +384,20 @@ impl Sub for Duration { } } +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + let new = self.checked_add(&rhs).expect("`Duration + Duration` overflowed"); + *self = new; + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + let new = self.checked_sub(&rhs).expect("`Duration - Duration` overflowed"); + *self = new; + } +} + impl Mul for Duration { type Output = Duration; @@ -533,6 +547,11 @@ mod tests { -(Duration::days(3) + Duration::seconds(70)), Duration::days(-4) + Duration::seconds(86_400 - 70) ); + + let mut d = Duration::default(); + d += Duration::minutes(1); + d -= Duration::seconds(30); + assert_eq!(d, Duration::seconds(30)); } #[test] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 4882bb69b0..965834ce95 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -590,11 +590,11 @@ impl NaiveTime { if frac >= 1_000_000_000 { let rfrac = 2_000_000_000 - frac; if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs = rhs - OldDuration::nanoseconds(i64::from(rfrac)); + rhs -= OldDuration::nanoseconds(i64::from(rfrac)); secs += 1; frac = 0; } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs = rhs + OldDuration::nanoseconds(i64::from(frac)); + rhs += OldDuration::nanoseconds(i64::from(frac)); frac = 0; } else { frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; From a0d3800412a989599cdabf748628e2371894b04e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 8 Sep 2023 21:44:43 +0200 Subject: [PATCH 32/57] (Used in `NaiveDate::timestamp*`) Make methods on `NaiveDateTime` const where possible --- src/naive/date.rs | 23 +++++++++++++++ src/naive/datetime/mod.rs | 59 ++++++++++++++++++++------------------- src/naive/time/mod.rs | 15 ++++++++++ 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 379e667a56..69f413edb7 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -1437,6 +1437,13 @@ impl NaiveDate { self.ymdf >> 13 } + /// Returns the day of year starting from 1. + // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. + #[inline] + const fn ordinal(&self) -> u32 { + self.of().ordinal() + } + // This duplicates `Datelike::month()`, because trait methods can't be const yet. #[inline] const fn month(&self) -> u32 { @@ -1455,6 +1462,22 @@ impl NaiveDate { self.of().weekday() } + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. + pub(crate) const fn num_days_from_ce(&self) -> i32 { + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o12 /*D*/ }; /// The maximum possible `NaiveDate` (December 31, 262142 CE). diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index e6558de2e4..6be8c88ce5 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -126,9 +126,9 @@ impl NaiveDateTime { #[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")] #[inline] #[must_use] - pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { + pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); - datetime.expect("invalid or out-of-range datetime") + expect!(datetime, "invalid or out-of-range datetime") } /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. @@ -157,7 +157,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_millis(millis: i64) -> Option { + pub const fn from_timestamp_millis(millis: i64) -> Option { let secs = millis.div_euclid(1000); let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; NaiveDateTime::from_timestamp_opt(secs, nsecs) @@ -189,7 +189,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_micros(micros: i64) -> Option { + pub const fn from_timestamp_micros(micros: i64) -> Option { let secs = micros.div_euclid(1_000_000); let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; NaiveDateTime::from_timestamp_opt(secs, nsecs) @@ -227,13 +227,14 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { + pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { let days = secs.div_euclid(86_400); let secs = secs.rem_euclid(86_400); - let date = i32::try_from(days) - .ok() - .and_then(|days| days.checked_add(719_163)) - .and_then(NaiveDate::from_num_days_from_ce_opt); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + let date = + NaiveDate::from_num_days_from_ce_opt(try_opt!((days as i32).checked_add(719_163))); let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); match (date, time) { (Some(date), Some(time)) => Some(NaiveDateTime { date, time }), @@ -397,10 +398,10 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp(&self) -> i64 { + pub const fn timestamp(&self) -> i64 { const UNIX_EPOCH_DAY: i64 = 719_163; - let gregorian_day = i64::from(self.date.num_days_from_ce()); - let seconds_from_midnight = i64::from(self.time.num_seconds_from_midnight()); + let gregorian_day = self.date.num_days_from_ce() as i64; + let seconds_from_midnight = self.time.num_seconds_from_midnight() as i64; (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight } @@ -425,9 +426,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_millis(&self) -> i64 { + pub const fn timestamp_millis(&self) -> i64 { let as_ms = self.timestamp() * 1000; - as_ms + i64::from(self.timestamp_subsec_millis()) + as_ms + self.timestamp_subsec_millis() as i64 } /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. @@ -448,9 +449,9 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_micros(&self) -> i64 { + pub const fn timestamp_micros(&self) -> i64 { let as_us = self.timestamp() * 1_000_000; - as_us + i64::from(self.timestamp_subsec_micros()) + as_us + self.timestamp_subsec_micros() as i64 } /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. @@ -547,7 +548,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_millis(&self) -> u32 { + pub const fn timestamp_subsec_millis(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000_000 } @@ -569,7 +570,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_micros(&self) -> u32 { + pub const fn timestamp_subsec_micros(&self) -> u32 { self.timestamp_subsec_nanos() / 1_000 } @@ -591,7 +592,7 @@ impl NaiveDateTime { /// ``` #[inline] #[must_use] - pub fn timestamp_subsec_nanos(&self) -> u32 { + pub const fn timestamp_subsec_nanos(&self) -> u32 { self.time.nanosecond() } @@ -671,7 +672,7 @@ impl NaiveDateTime { return None; } - let date = self.date.checked_add_signed(OldDuration::seconds(rhs))?; + let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); Some(NaiveDateTime { date, time }) } @@ -701,8 +702,8 @@ impl NaiveDateTime { /// ); /// ``` #[must_use] - pub fn checked_add_months(self, rhs: Months) -> Option { - Some(Self { date: self.date.checked_add_months(rhs)?, time: self.time }) + pub const fn checked_add_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_months(rhs)), time: self.time }) } /// Adds given `FixedOffset` to the current datetime. @@ -854,24 +855,24 @@ impl NaiveDateTime { /// ); /// ``` #[must_use] - pub fn checked_sub_months(self, rhs: Months) -> Option { - Some(Self { date: self.date.checked_sub_months(rhs)?, time: self.time }) + pub const fn checked_sub_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_months(rhs)), time: self.time }) } /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. #[must_use] - pub fn checked_add_days(self, days: Days) -> Option { - Some(Self { date: self.date.checked_add_days(days)?, ..self }) + pub const fn checked_add_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_days(days)), ..self }) } /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` /// /// Returns `None` if the resulting date would be out of range. #[must_use] - pub fn checked_sub_days(self, days: Days) -> Option { - Some(Self { date: self.date.checked_sub_days(days)?, ..self }) + pub const fn checked_sub_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_days(days)), ..self }) } /// Subtracts another `NaiveDateTime` from the current date and time. @@ -1430,7 +1431,7 @@ impl Timelike for NaiveDateTime { /// The range from 1,000,000,000 to 1,999,999,999 represents /// the [leap second](./struct.NaiveTime.html#leap-second-handling). /// - /// See also the [`NaiveTime::nanosecond`] method. + /// See also the [`NaiveTime#method.nanosecond`] method. /// /// # Example /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 965834ce95..de29fa465e 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -868,6 +868,21 @@ impl NaiveTime { (hour, min, sec) } + /// Returns the number of non-leap seconds past the last midnight. + // This duplicates `Timelike::num_seconds_from_midnight()`, because trait methods can't be const + // yet. + #[inline] + pub(crate) const fn num_seconds_from_midnight(&self) -> u32 { + self.secs + } + + /// Returns the number of nanoseconds since the whole non-leap second. + // This duplicates `Timelike::nanosecond()`, because trait methods can't be const yet. + #[inline] + pub(crate) const fn nanosecond(&self) -> u32 { + self.frac + } + /// The earliest possible `NaiveTime` pub const MIN: Self = Self { secs: 0, frac: 0 }; pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; From fe4713a4f06728096c4a27a31b5de01c11b9e4e6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 13 Sep 2023 07:26:06 +0200 Subject: [PATCH 33/57] Make `NaiveDateTime::and_utc` const --- src/datetime/mod.rs | 8 ++++++++ src/naive/datetime/mod.rs | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 24a344d9ab..b26296823e 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -622,6 +622,14 @@ impl DateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) } + // FIXME: remove when our MSRV is 1.61+ + // This method is used by `NaiveDateTime::and_utc` because `DateTime::from_naive_utc_and_offset` + // can't be made const yet. + // Trait bounds in const function / implementation blocks were not supported until 1.61. + pub(crate) const fn from_naive_utc(datetime: NaiveDateTime) -> Self { + DateTime { datetime, offset: Utc } + } + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 6be8c88ce5..88d780a4c1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1030,8 +1030,9 @@ impl NaiveDateTime { /// assert_eq!(dt.timezone(), Utc); /// ``` #[must_use] - pub fn and_utc(&self) -> DateTime { - Utc.from_utc_datetime(self) + pub const fn and_utc(&self) -> DateTime { + // FIXME: use `DateTime::from_naive_utc_and_offset` when our MSRV is 1.61+. + DateTime::from_naive_utc(*self) } /// The minimum possible `NaiveDateTime`. From 0f19d6be1db7b0e2a8528d57654317e1f9c8084f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 29 Sep 2023 12:43:38 +0200 Subject: [PATCH 34/57] Adjust `Duration::MIN` by 1 millisecond --- src/duration.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index fb64591b80..1e5eb67512 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -61,10 +61,10 @@ pub struct Duration { nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC } -/// The minimum possible `Duration`: `i64::MIN` milliseconds. +/// The minimum possible `Duration`: `-i64::MAX` milliseconds. pub(crate) const MIN: Duration = Duration { - secs: i64::MIN / MILLIS_PER_SEC - 1, - nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, + secs: -i64::MAX / MILLIS_PER_SEC - 1, + nanos: NANOS_PER_SEC + (-i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, }; /// The maximum possible `Duration`: `i64::MAX` milliseconds. @@ -144,7 +144,7 @@ impl Duration { /// Makes a new `Duration` with given number of seconds. /// Panics when the duration is more than `i64::MAX` milliseconds - /// or less than `i64::MIN` milliseconds. + /// or less than `-i64::MAX` milliseconds. #[inline] #[must_use] pub fn seconds(seconds: i64) -> Duration { @@ -153,7 +153,7 @@ impl Duration { /// Makes a new `Duration` with given number of seconds. /// Returns `None` when the duration is more than `i64::MAX` milliseconds - /// or less than `i64::MIN` milliseconds. + /// or less than `-i64::MAX` milliseconds. #[inline] pub fn try_seconds(seconds: i64) -> Option { let d = Duration { secs: seconds, nanos: 0 }; @@ -303,7 +303,7 @@ impl Duration { } } - /// The minimum possible `Duration`: `i64::MIN` milliseconds. + /// The minimum possible `Duration`: `-i64::MAX` milliseconds. #[inline] pub const fn min_value() -> Duration { MIN @@ -507,7 +507,7 @@ const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for Duration { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { - const MIN_SECS: i64 = i64::MIN / MILLIS_PER_SEC - 1; + const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; @@ -588,9 +588,9 @@ mod tests { assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN); + assert_eq!(Duration::milliseconds(-i64::MAX).num_milliseconds(), -i64::MAX); assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), i64::MIN); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); } #[test] @@ -603,7 +603,7 @@ mod tests { assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); + assert_eq!(Duration::microseconds(-i64::MAX).num_microseconds(), Some(-i64::MAX)); assert_eq!(MAX.num_microseconds(), None); assert_eq!(MIN.num_microseconds(), None); @@ -614,11 +614,11 @@ mod tests { Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!( - Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), - Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY) + Duration::days(-i64::MAX / MICROS_PER_DAY).num_microseconds(), + Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) ); assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); + assert_eq!(Duration::days(-i64::MAX / MICROS_PER_DAY - 1).num_microseconds(), None); } #[test] @@ -627,7 +627,7 @@ mod tests { assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); + assert_eq!(Duration::nanoseconds(-i64::MAX).num_nanoseconds(), Some(-i64::MAX)); assert_eq!(MAX.num_nanoseconds(), None); assert_eq!(MIN.num_nanoseconds(), None); @@ -638,11 +638,11 @@ mod tests { Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!( - Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY) + Duration::days(-i64::MAX / NANOS_PER_DAY).num_nanoseconds(), + Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) ); assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); + assert_eq!(Duration::days(-i64::MAX / NANOS_PER_DAY - 1).num_nanoseconds(), None); } #[test] @@ -656,10 +656,12 @@ mod tests { .is_none()); assert_eq!( - Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)), - Some(Duration::milliseconds(i64::MIN)) + Duration::milliseconds(-i64::MAX).checked_sub(&Duration::milliseconds(0)), + Some(Duration::milliseconds(-i64::MAX)) ); - assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none()); + assert!(Duration::milliseconds(-i64::MAX) + .checked_sub(&Duration::milliseconds(1)) + .is_none()); } #[test] @@ -673,6 +675,7 @@ mod tests { assert_eq!(Duration::milliseconds(-1000).abs(), Duration::milliseconds(1000)); assert_eq!(Duration::milliseconds(-1300).abs(), Duration::milliseconds(1300)); assert_eq!(Duration::milliseconds(-1700).abs(), Duration::milliseconds(1700)); + assert_eq!(Duration::milliseconds(-i64::MAX).abs(), Duration::milliseconds(i64::MAX)); } #[test] From 02bdd1ddc8750760c9e2e89aa3c1d2012c743417 Mon Sep 17 00:00:00 2001 From: Mikhail Katychev Date: Tue, 10 Oct 2023 09:25:06 -0500 Subject: [PATCH 35/57] archive derive of PartialEq for rkyv - 0.4.x (#959) --- .github/workflows/test.yml | 2 +- Cargo.toml | 3 ++- src/date.rs | 2 +- src/datetime/mod.rs | 27 +++++++++++++++++++++++++++ src/duration.rs | 10 ++++++++++ src/format/scan.rs | 2 +- src/month.rs | 10 ++++++++++ src/naive/date.rs | 14 ++++++++++++++ src/naive/datetime/mod.rs | 2 ++ src/naive/datetime/tests.rs | 12 ++++++++++++ src/naive/isoweek.rs | 16 ++++++++++++++++ src/naive/time/mod.rs | 2 ++ src/naive/time/tests.rs | 12 ++++++++++++ src/offset/fixed.rs | 15 ++++++++++++++- src/offset/local/mod.rs | 16 +++++++++++++++- src/offset/utc.rs | 7 ++++++- src/weekday.rs | 16 +++++++++++++++- 17 files changed, 160 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1bdd2a4c2..a096504d97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 - run: | - cargo hack check --feature-powerset --optional-deps serde,rkyv \ + cargo hack check --feature-powerset --optional-deps serde \ --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ --all-targets # run using `bash` on all platforms for consistent diff --git a/Cargo.toml b/Cargo.toml index 7d82ba3eca..c6edb2a311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] +rkyv-validation = ["rkyv/validation"] __internal_bench = [] [dependencies] @@ -33,7 +34,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.7", optional = true } -rkyv = { version = "0.7", optional = true } +rkyv = { version = "0.7.41", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] diff --git a/src/date.rs b/src/date.rs index e82a3f0eee..8a8511b528 100644 --- a/src/date.rs +++ b/src/date.rs @@ -479,7 +479,7 @@ impl Eq for Date {} impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { - self.date.partial_cmp(&other.date) + Some(self.cmp(other)) } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b26296823e..ac9ef511d0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -51,6 +51,8 @@ mod tests; /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. #[derive(Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] +#[cfg_attr(feature = "rkyv", archive(compare(PartialEq, PartialOrd)))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct DateTime { datetime: NaiveDateTime, offset: Tz::Offset, @@ -1504,6 +1506,31 @@ impl fmt::Debug for DateTime { } } +// `fmt::Debug` is hand implemented for the `rkyv::Archive` variant of `DateTime` because +// deriving a trait recursively does not propagate trait defined associated types with their own +// constraints: +// In our case `<::Offset as Archive>::Archived` +// cannot be formatted using `{:?}` because it doesn't implement `Debug`. +// See below for further discussion: +// * https://github.com/rust-lang/rust/issues/26925 +// * https://github.com/rkyv/rkyv/issues/333 +// * https://github.com/dtolnay/syn/issues/370 +#[cfg(feature = "rkyv-validation")] +impl fmt::Debug for ArchivedDateTime +where + Tz: Archive, + ::Archived: fmt::Debug, + <::Offset as Archive>::Archived: fmt::Debug, + ::Offset: fmt::Debug + Archive, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ArchivedDateTime") + .field("datetime", &self.datetime) + .field("offset", &self.offset) + .finish() + } +} + impl fmt::Display for DateTime where Tz::Offset: fmt::Display, diff --git a/src/duration.rs b/src/duration.rs index 1e5eb67512..f00b03663e 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -54,8 +54,10 @@ macro_rules! try_opt { #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct Duration { secs: i64, nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC @@ -795,4 +797,12 @@ mod tests { Err(OutOfRangeError(())) ); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let duration = Duration::seconds(1); + let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); + } } diff --git a/src/format/scan.rs b/src/format/scan.rs index 98f3673ff1..45b5bcbec0 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -274,7 +274,7 @@ where }; s = match s.len() { len if len >= 2 => &s[2..], - len if len == 0 => s, + 0 => s, _ => return Err(TOO_SHORT), }; diff --git a/src/month.rs b/src/month.rs index a409bb155d..26597bd4b0 100644 --- a/src/month.rs +++ b/src/month.rs @@ -33,8 +33,10 @@ use crate::OutOfRange; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { /// January @@ -417,4 +419,12 @@ mod tests { from_str::(string).unwrap_err(); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let month = Month::January; + let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), month); + } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 69f413edb7..11952e2766 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -192,8 +192,10 @@ impl Days { #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveDate { ymdf: DateImpl, // (year << 13) | of } @@ -3324,6 +3326,18 @@ mod tests { } } + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let date_min = NaiveDate::MIN; + let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); + + let date_max = NaiveDate::MAX; + let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); + } + // MAX_YEAR-12-31 minus 0000-01-01 // = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) // = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 88d780a4c1..2f9b2db6d1 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -77,8 +77,10 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct NaiveDateTime { date: NaiveDate, diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 97604db74f..0dbbe7bc5e 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -536,3 +536,15 @@ fn test_and_timezone_min_max_dates() { } } } + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let dt_min = NaiveDateTime::MIN; + let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_min); + + let dt_max = NaiveDateTime::MAX; + let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_max); +} diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 606699659b..50ffcdc169 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -20,8 +20,10 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. // this is crucial because we have an edge case for the first and last week supported, @@ -151,6 +153,8 @@ impl fmt::Debug for IsoWeek { #[cfg(test)] mod tests { + #[cfg(feature = "rkyv-validation")] + use super::IsoWeek; use crate::naive::{internals, NaiveDate}; use crate::Datelike; @@ -205,4 +209,16 @@ mod tests { assert!(monday.iso_week() >= friday.iso_week()); assert!(monday.iso_week() <= friday.iso_week()); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let minweek = NaiveDate::MIN.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), minweek); + + let maxweek = NaiveDate::MAX.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), maxweek); + } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index de29fa465e..9a2e5d8f76 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -206,8 +206,10 @@ mod tests; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct NaiveTime { secs: u32, frac: u32, diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 30c1af9537..784a8390b8 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -376,3 +376,15 @@ fn test_overflowing_offset() { assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset); assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset); } + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let t_min = NaiveTime::MIN; + let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_min); + + let t_max = NaiveTime::MAX; + let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_max); +} diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 6d3aebdb68..1826ec4ec2 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -21,7 +21,12 @@ use crate::naive::{NaiveDate, NaiveDateTime}; /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct FixedOffset { local_minus_utc: i32, } @@ -222,4 +227,12 @@ mod tests { let offset = FixedOffset::from_str("+06:30").unwrap(); assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let offset = FixedOffset::from_str("-0500").unwrap(); + let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), offset); + } } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index e4f4e1631f..af1c21e7ef 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -105,7 +105,8 @@ mod tz_info; /// ``` #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr(feature = "rkyv", archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; @@ -258,4 +259,17 @@ mod tests { ); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let local = Local; + // Local is a ZST and serializes to 0 bytes + let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap(); + assert_eq!(bytes.len(), 0); + + // but is deserialized to an archived variant without a + // wrapping object + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), super::ArchivedLocal); + } } diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 3b0a85d56a..11e9dad931 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -42,7 +42,12 @@ use crate::{Date, DateTime}; /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; diff --git a/src/weekday.rs b/src/weekday.rs index fb88c3b929..786d516c7b 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -32,7 +32,12 @@ use crate::OutOfRange; #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))] +#[cfg_attr( + feature = "rkyv", + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Weekday { /// Monday. @@ -381,4 +386,13 @@ mod tests { from_str::(str).unwrap_err(); } } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let mon = Weekday::Mon; + let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); + + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), mon); + } } From 15c8885365c696cf7a43cd9ead507974a2e92f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 04:27:03 +0000 Subject: [PATCH 36/57] Bump actions/setup-node from 3 to 4 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a096504d97..8deb2a8533 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,7 +135,7 @@ jobs: with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 - uses: jetli/wasm-pack-action@v0.4.0 # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment # with the host system. From fbd67ab02cf4b41b476e8e43bfe501c2b81dc35e Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:29:25 -0800 Subject: [PATCH 37/57] test.yml remove errant `with: node-version` --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8deb2a8533..c6c3ec1261 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -120,8 +120,6 @@ jobs: with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - with: - node-version: "12" - run: cargo build --target ${{ matrix.target }} --color=always test_wasm: From dd3f2f81c4cf6f4d85723f122d7e637bd4bd674d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 30 Oct 2023 09:30:11 +0100 Subject: [PATCH 38/57] Bump MSRV to 1.61 --- .github/workflows/test.yml | 4 ++-- Cargo.toml | 2 +- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6c3ec1261..67e6e8c696 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - run: cargo test --all-features --color=always -- --color=always # later this may be able to be included with the below - # kept separate for now as the following don't compile on 1.57 + # kept separate for now as the following don't compile on 1.60 # * arbitrary (requires 1.63 as of v1.3.0) rust_msrv: strategy: @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.57 + toolchain: "1.61.0" - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests # which are run elsewhere diff --git a/Cargo.toml b/Cargo.toml index c6edb2a311..d960da1f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" exclude = ["/ci/*"] edition = "2021" -rust-version = "1.57.0" +rust-version = "1.61.0" [lib] name = "chrono" diff --git a/README.md b/README.md index c3222ddaa3..f605b11f22 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Optional features: ## Rust version requirements -The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. +The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done lightly. diff --git a/src/lib.rs b/src/lib.rs index da8e00d17a..64d2135e6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -359,7 +359,7 @@ //! //! ## Rust version requirements //! -//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**. +//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. //! //! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done //! lightly. From c0f418bbbfa83b1c8392099cec9fdf42657c1e51 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sat, 28 Oct 2023 10:42:33 -0600 Subject: [PATCH 39/57] Split clock feature into clock and now --- .github/workflows/test.yml | 1 + Cargo.toml | 3 ++- README.md | 3 ++- src/lib.rs | 17 +++++++++++++---- src/offset/utc.rs | 6 +++--- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67e6e8c696..19232e0e8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,7 @@ jobs: - run: cargo test --no-default-features --features=alloc - run: cargo test --no-default-features --features=unstable-locales - run: cargo test --no-default-features --features=alloc,unstable-locales + - run: cargo test --no-default-features --features=now no_std: strategy: diff --git a/Cargo.toml b/Cargo.toml index d960da1f4e..1cad234834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ alloc = [] libc = [] winapi = ["windows-targets"] std = ["alloc"] -clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] +clock = ["winapi", "iana-time-zone", "android-tzdata", "now"] +now = ["std"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] diff --git a/README.md b/README.md index f605b11f22..c75a50096f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ Default features: * `alloc`: Enable features that depend on allocation (primarily string formatting) * `std`: Enables functionality that depends on the standard library. This is a superset of `alloc` and adds interoperation with standard library types and traits. -* `clock`: Enables reading the system time (`now`) and local timezone (`Local`). +* `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +* `now`: Enables reading the system time (`now`) * `wasmbind`: Interface with the JS Date API for the `wasm32` target. Optional features: diff --git a/src/lib.rs b/src/lib.rs index 64d2135e6c..086dbe450c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,21 +108,28 @@ //! or in the local time zone //! ([`Local::now()`](./offset/struct.Local.html#method.now)). //! +#![cfg_attr(not(feature = "now"), doc = "```ignore")] +#![cfg_attr(feature = "now", doc = "```rust")] +//! use chrono::prelude::*; +//! +//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` +//! # let _ = utc; +//! ``` +//! #![cfg_attr(not(feature = "clock"), doc = "```ignore")] #![cfg_attr(feature = "clock", doc = "```rust")] //! use chrono::prelude::*; //! -//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` //! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -//! # let _ = utc; let _ = local; +//! # let _ = local; //! ``` //! //! Alternatively, you can create your own date and time. //! This is a bit verbose due to Rust's lack of function and method overloading, //! but in turn we get a rich combination of initialization methods. //! -#![cfg_attr(not(feature = "std"), doc = "```ignore")] -#![cfg_attr(feature = "std", doc = "```rust")] +#![cfg_attr(not(feature = "now"), doc = "```ignore")] +#![cfg_attr(feature = "now", doc = "```rust")] //! use chrono::prelude::*; //! use chrono::offset::LocalResult; //! @@ -146,12 +153,14 @@ //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None); //! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None); //! +//! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. //! let local_dt = Local.from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap()).unwrap(); //! let fixed_dt = FixedOffset::east_opt(9 * 3600).unwrap().from_local_datetime(&NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(18, 10, 11, 12).unwrap()).unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; +//! # } //! # Some(()) //! # } //! # doctest().unwrap(); diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 11e9dad931..7c23a282bb 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -5,7 +5,7 @@ use core::fmt; #[cfg(all( - feature = "clock", + feature = "now", not(all( target_arch = "wasm32", feature = "wasmbind", @@ -19,7 +19,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::naive::{NaiveDate, NaiveDateTime}; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] #[allow(deprecated)] use crate::{Date, DateTime}; @@ -51,7 +51,7 @@ use crate::{Date, DateTime}; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] impl Utc { /// Returns a `Date` which corresponds to the current date. #[deprecated( From 039189c4a1360db117898bf3d2bbddce3bc1d4a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 04:17:46 +0000 Subject: [PATCH 40/57] Update windows-targets requirement from 0.48 to 0.52 Updates the requirements on [windows-targets](https://github.com/microsoft/windows-rs) to permit the latest version. - [Release notes](https://github.com/microsoft/windows-rs/releases) - [Commits](https://github.com/microsoft/windows-rs/compare/windows-targets-0.48.1...0.52.0) --- updated-dependencies: - dependency-name: windows-targets dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1cad234834..e056373386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for [target.'cfg(windows)'.dependencies] -windows-targets = { version = "0.48", optional = true } +windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] windows-bindgen = { version = "0.51" } From 6f3ab9dd5a5120871ccb524fb6d0eb697645eab1 Mon Sep 17 00:00:00 2001 From: dcechano Date: Sun, 12 Nov 2023 16:23:51 -0500 Subject: [PATCH 41/57] Add From for NaiveDateTime --- src/naive/datetime/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 2f9b2db6d1..51b9988a75 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1048,6 +1048,22 @@ impl NaiveDateTime { expect!(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); } +impl From for NaiveDateTime { + /// Converts a `NaiveDate` to a `NaiveDateTime` of the same date but at midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let nd = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap(); + /// let ndt = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// assert_eq!(ndt, NaiveDateTime::from(nd)); + fn from(date: NaiveDate) -> Self { + date.and_hms_opt(0, 0, 0).unwrap() + } +} + impl Datelike for NaiveDateTime { /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). /// From e1a9494e24b24d4c754aa889a4be6f386ad37d16 Mon Sep 17 00:00:00 2001 From: Ali Mirghasemi Date: Fri, 24 Nov 2023 12:08:25 +0330 Subject: [PATCH 42/57] Add from_timestamp_nanos (#1357) * Add from_timestamp_nanos Add and implement from_timestamp_nanos and add unit test for it * Fix lint check error * Add pub(crate) for NANOS_PER_SEC * Replace number with constant variable --- src/duration.rs | 2 +- src/naive/datetime/mod.rs | 35 ++++++++++++++++++++++++- src/naive/datetime/tests.rs | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/duration.rs b/src/duration.rs index f00b03663e..63fa66fd49 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -24,7 +24,7 @@ const NANOS_PER_MICRO: i32 = 1000; /// The number of nanoseconds in a millisecond. const NANOS_PER_MILLI: i32 = 1_000_000; /// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; +pub(crate) const NANOS_PER_SEC: i32 = 1_000_000_000; /// The number of microseconds per second. const MICROS_PER_SEC: i64 = 1_000_000; /// The number of milliseconds per second. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 51b9988a75..764a1743a8 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -13,7 +13,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -use crate::duration::Duration as OldDuration; +use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; #[cfg(feature = "alloc")] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -197,6 +197,39 @@ impl NaiveDateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs) } + /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDateTime; + /// let timestamp_nanos: i64 = 1662921288_000_000_000; //Sunday, September 11, 2022 6:34:48 PM + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; //Mon Jan 01 1900 14:38:45 GMT+0000 + /// let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos); + /// assert!(naive_datetime.is_some()); + /// assert_eq!(timestamp_nanos, naive_datetime.unwrap().timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Option { + let secs = nanos.div_euclid(NANOS_PER_SEC as i64); + let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; + + NaiveDateTime::from_timestamp_opt(secs, nsecs) + } + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, /// from the number of non-leap seconds /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 0dbbe7bc5e..6375a9d351 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -78,6 +78,57 @@ fn test_datetime_from_timestamp_micros() { } } +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let naive_datetime = NaiveDateTime::from_timestamp_nanos(timestamp_nanos).unwrap(); + assert_eq!(timestamp_nanos, naive_datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(naive_datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000"; + let parsed: NaiveDateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + NaiveDateTime::from_timestamp_nanos(nanos).unwrap(), + NaiveDateTime::from_timestamp_opt(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Test that the result of `from_timestamp_nanos` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + NaiveDateTime::from_timestamp_nanos(secs * 1_000_000_000), + NaiveDateTime::from_timestamp_opt(secs, 0) + ); + } +} + #[test] fn test_datetime_from_timestamp() { let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); From d7b4a82027471896caa175b07918fe883a5bef58 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 20 Nov 2023 09:26:05 +0100 Subject: [PATCH 43/57] Silence clippy lint for test code --- src/datetime/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 42b014983d..3df11e78d4 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -412,6 +412,7 @@ fn signed_duration_since_autoref() { let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap(); let diff1 = dt1.signed_duration_since(dt2); // Copy/consume + #[allow(clippy::needless_borrows_for_generic_args)] let diff2 = dt2.signed_duration_since(&dt1); // Take by reference assert_eq!(diff1, -diff2); From 3f2282208674f01fc3cb2fde4ada38eaf91bcdc5 Mon Sep 17 00:00:00 2001 From: Gerd Zellweger Date: Fri, 1 Dec 2023 11:08:21 -0800 Subject: [PATCH 44/57] Expose rkyv features as features for chrono users. rkyv by default serializes usize as u32. This isn't ideal on most modern platforms and unfortunately is configured through a feature flag. If we just set `default-features = false` in the rkyv Cargo dependency, the crate fails to compile because all the size features are mutually exclusive. On the other hand if we want to e.g., change the serialization of usize to 64-bit and we also want to use chrono this currently fails to compile because chrono always enables rkyv/size_32. This re-exports the relevant rkyv features so users can choose which serialization to enable. The approach is similar to what the ordered-float crate does: https://github.com/reem/rust-ordered-float/blob/8111b345372632893af0b8aa12152f3dc7278aba/Cargo.toml#L37 Signed-off-by: Gerd Zellweger --- Cargo.toml | 10 ++++++++-- src/month.rs | 2 +- src/naive/isoweek.rs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e056373386..a56c0c000f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.61.0" name = "chrono" [features] +# Don't forget to adjust `ALL_NON_EXCLUSIVE_FEATURES` in CI scripts when adding a feature or an optional dependency. default = ["clock", "std", "oldtime", "wasmbind"] alloc = [] libc = [] @@ -27,7 +28,12 @@ now = ["std"] oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] -rkyv-validation = ["rkyv/validation"] +# Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive. +rkyv-16 = ["rkyv", "rkyv?/size_16"] +rkyv-32 = ["rkyv", "rkyv?/size_32"] +rkyv-64 = ["rkyv", "rkyv?/size_64"] +rkyv-validation = ["rkyv?/validation"] +# Features for internal use only: __internal_bench = [] [dependencies] @@ -35,7 +41,7 @@ num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.7", optional = true } -rkyv = { version = "0.7.41", optional = true } +rkyv = { version = "0.7.43", optional = true, default-features = false } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] diff --git a/src/month.rs b/src/month.rs index 26597bd4b0..2f18f9a10b 100644 --- a/src/month.rs +++ b/src/month.rs @@ -33,7 +33,7 @@ use crate::OutOfRange; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", - archive(compare(PartialEq)), + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 50ffcdc169..8ef982701e 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -20,7 +20,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( feature = "rkyv", - archive(compare(PartialEq)), + archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] From 849932b09bdc58c508c0b6794fa474a9558d27ea Mon Sep 17 00:00:00 2001 From: Gerd Zellweger Date: Thu, 14 Dec 2023 14:26:01 -0800 Subject: [PATCH 45/57] Change the CI `--all-features` to an explicit list. This is due to the mutually exclusive features in rkyv which we expose now. `--all-features` will now activate them and the crate will fail to compile rkyv. We work around this by defining an explicit list of all mutually exclusive features to. Unfortunately there isn't an easy way to share env variables among different YAML files (https://github.com/actions/runner/issues/655). There also isn't a good way to specify `--all-features` minus "just a few" (https://github.com/rust-lang/cargo/issues/3126) aside from giving the complete list. Signed-off-by: Gerd Zellweger --- .github/workflows/codecov.yml | 7 ++++++- .github/workflows/lint.yml | 10 +++++++--- .github/workflows/test.yml | 22 +++++++++++++--------- Cargo.toml | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 88634264cf..a819b21a5b 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -1,4 +1,9 @@ name: codecov + +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -18,7 +23,7 @@ jobs: - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo +nightly llvm-cov --all-features --workspace --lcov --doctests --output-path lcov.info + run: cargo +nightly llvm-cov ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --workspace --lcov --doctests --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87c9f553c4..b954a7ec2c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,9 @@ name: lint +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-64 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -19,7 +23,7 @@ jobs: - run: cargo fmt --check --manifest-path fuzz/Cargo.toml - run: cargo fmt --check --manifest-path bench/Cargo.toml - run: | - cargo clippy --all-features --all-targets --color=always \ + cargo clippy ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --all-targets --color=always \ -- -D warnings - run: | cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ @@ -50,8 +54,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-deadlinks - - run: cargo deadlinks -- --all-features - - run: cargo doc --all-features --no-deps + - run: cargo deadlinks -- ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} + - run: cargo doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: RUSTDOCFLAGS: -Dwarnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19232e0e8f..34aae224bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,9 @@ name: All Tests and Builds +env: + # It's really `--all-features`, but not adding the mutually exclusive features from rkyv + ALL_NON_EXCLUSIVE_FEATURES: --features "default unstable-locales rkyv-32 rkyv-validation rustc-serialize serde arbitrary" + on: push: branches: [main, 0.4.x] @@ -16,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - run: cargo test --all-features --color=always -- --color=always + - run: cargo test ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always # later this may be able to be included with the below # kept separate for now as the following don't compile on 1.60 @@ -61,8 +65,8 @@ jobs: - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets # run --lib and --doc to avoid the long running integration tests # which are run elsewhere - - run: cargo test --lib --all-features --color=always -- --color=always - - run: cargo test --doc --all-features --color=always -- --color=always + - run: cargo test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always + - run: cargo test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --color=always -- --color=always features_check: strategy: @@ -76,7 +80,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: | cargo hack check --feature-powerset --optional-deps serde \ - --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi \ + --skip __internal_bench,iana-time-zone,pure-rust-locales,libc,winapi,rkyv-16,rkyv-64,rkyv-validation \ --all-targets # run using `bash` on all platforms for consistent # line-continuation marks @@ -182,16 +186,16 @@ jobs: - uses: actions/checkout@v4 - run: cargo install cross - uses: Swatinem/rust-cache@v2 - - run: cross test --lib --all-features --target i686-unknown-linux-gnu --color=always - - run: cross test --doc --all-features --target i686-unknown-linux-gnu --color=always - - run: cross test --lib --all-features --target i686-unknown-linux-musl --color=always - - run: cross test --doc --all-features --target i686-unknown-linux-musl --color=always + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-gnu --color=always + - run: cross test --lib ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always + - run: cross test --doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --target i686-unknown-linux-musl --color=always check-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: cargo +nightly doc --all-features --no-deps + - run: cargo +nightly doc ${{ env.ALL_NON_EXCLUSIVE_FEATURES }} --no-deps env: RUSTDOCFLAGS: "-D warnings --cfg docsrs" diff --git a/Cargo.toml b/Cargo.toml index a56c0c000f..5fbc14cccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ unstable-locales = ["pure-rust-locales"] rkyv-16 = ["rkyv", "rkyv?/size_16"] rkyv-32 = ["rkyv", "rkyv?/size_32"] rkyv-64 = ["rkyv", "rkyv?/size_64"] -rkyv-validation = ["rkyv?/validation"] +rkyv-validation = ["rkyv", "rkyv?/validation"] # Features for internal use only: __internal_bench = [] From 6033afea6f6e986f8da8b58c1b363894ad2542a7 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Thu, 26 Oct 2023 00:42:49 -0600 Subject: [PATCH 46/57] docs: Improve deprecation of `TimeZone::datetime_from_str` Add recommending using `NaiveDate::parse_from_str` as well --- src/offset/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 99b64b2760..ae3634e549 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -469,7 +469,14 @@ pub trait TimeZone: Sized + Clone { /// /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. - #[deprecated(since = "0.4.29", note = "use `DateTime::parse_from_str` instead")] + /// + /// See also [`NaiveDateTime::parse_from_str`] which gives a [`NaiveDateTime`] without + /// an offset, but can be converted to a [`DateTime`] with [`NaiveDateTime::and_utc`] or + /// [`NaiveDateTime::and_local_timezone`]. + #[deprecated( + since = "0.4.29", + note = "use `DateTime::parse_from_str` or `NaiveDateTime::parse_from_str` with `and_utc()` or `and_local_timezone()` instead" + )] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; From 5536687c0d4dfae66130cc44c04e08d9f2797708 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 9 Jan 2024 12:24:41 +0000 Subject: [PATCH 47/57] Add Months::as_u32() (#1373) --- src/month.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/month.rs b/src/month.rs index 2f18f9a10b..1e1531d716 100644 --- a/src/month.rs +++ b/src/month.rs @@ -233,6 +233,12 @@ impl Months { pub const fn new(num: u32) -> Self { Self(num) } + + /// Returns the total number of months in the `Months` instance. + #[inline] + pub const fn as_u32(&self) -> u32 { + self.0 + } } /// An error resulting from reading `` value with `FromStr`. @@ -302,7 +308,7 @@ mod month_serde { #[cfg(test)] mod tests { use super::Month; - use crate::{Datelike, OutOfRange, TimeZone, Utc}; + use crate::{Datelike, Months, OutOfRange, TimeZone, Utc}; #[test] fn test_month_enum_try_from() { @@ -357,6 +363,13 @@ mod tests { assert!(Month::September > Month::March); } + #[test] + fn test_months_as_u32() { + assert_eq!(Months::new(0).as_u32(), 0); + assert_eq!(Months::new(1).as_u32(), 1); + assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX); + } + #[test] #[cfg(feature = "serde")] fn test_serde_serialize() { From 65f0cc2aa41800a93878dfbeb816b182cc027b1d Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 30 Dec 2023 19:54:40 +0100 Subject: [PATCH 48/57] CI Linting: Fix missing sources checkout in `toml` job. --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b954a7ec2c..18f65d78e2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,6 +39,7 @@ jobs: container: image: tamasfe/taplo:0.8.0 steps: + - uses: actions/checkout@v4 - run: taplo lint - run: taplo fmt --check --diff From 6ec8f97d16ce320a6f948b1cd1494ed3a21b251f Mon Sep 17 00:00:00 2001 From: Makro <4398091+xmakro@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:50:58 +0800 Subject: [PATCH 49/57] Add from_timestamp_millis to DateTime (#1374) Co-authored-by: xmakro --- src/datetime/mod.rs | 28 ++++++++++++++++++++++++++++ src/datetime/tests.rs | 12 ++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index ac9ef511d0..9d4e1c3861 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -624,6 +624,34 @@ impl DateTime { NaiveDateTime::from_timestamp_opt(secs, nsecs).as_ref().map(NaiveDateTime::and_utc) } + /// Makes a new [`DateTime`] from the number of non-leap milliseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with regard to [`timestamp_millis`](DateTime::timestamp_millis). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of milliseconds, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::{DateTime, Utc}; + /// + /// let dt: DateTime = DateTime::::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); + /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub fn from_timestamp_millis(millis: i64) -> Option { + NaiveDateTime::from_timestamp_millis(millis).as_ref().map(NaiveDateTime::and_utc) + } + // FIXME: remove when our MSRV is 1.61+ // This method is used by `NaiveDateTime::and_utc` because `DateTime::from_naive_utc_and_offset` // can't be made const yet. diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3df11e78d4..3e96e227fa 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1253,6 +1253,18 @@ fn test_datetime_from_local() { assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); } +#[test] +fn test_datetime_from_timestamp_millis() { + // 2000-01-12T01:02:03:004Z + let naive_dt = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_milli_opt(1, 2, 3, 4).unwrap(); + let datetime_utc = DateTime::::from_naive_utc_and_offset(naive_dt, Utc); + assert_eq!( + datetime_utc, + DateTime::::from_timestamp_millis(datetime_utc.timestamp_millis()).unwrap() + ); +} + #[test] #[cfg(feature = "clock")] fn test_years_elapsed() { From cee242a6565bffcc2a90c8566136c3b96491a821 Mon Sep 17 00:00:00 2001 From: Tam Pham Date: Thu, 18 Jan 2024 11:14:31 -0600 Subject: [PATCH 50/57] Fix typos in Datelike impl for DateTime --- src/datetime/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 9d4e1c3861..6b3e49f5f4 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1045,7 +1045,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_month0(month0)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of month (starting from 1) changed. /// /// See also the [`NaiveDate::with_day`] method. /// @@ -1061,7 +1061,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_day(day)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of month (starting from 0) changed. /// /// See also the [`NaiveDate::with_day0`] method. /// @@ -1077,7 +1077,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_day0(day0)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of year (starting from 1) changed. /// /// See also the [`NaiveDate::with_ordinal`] method. /// @@ -1093,7 +1093,7 @@ impl Datelike for DateTime { map_local(self, |datetime| datetime.with_ordinal(ordinal)) } - /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// Makes a new `DateTime` with the day of year (starting from 0) changed. /// /// See also the [`NaiveDate::with_ordinal0`] method. /// From 7757386368913e8df9a8505f9ef37c5868045a88 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 22 Jan 2024 17:07:25 +0100 Subject: [PATCH 51/57] Prepare 0.4.32 release --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 877b1b48b4..7546aff126 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2023-09-15 -version: 0.4.31 +date-released: 2024-01-22 +version: 0.4.32 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index 5fbc14cccc..0d60e706f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.31" +version = "0.4.32" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From bf704191f2914a2db43abf6827a73cbe05864d57 Mon Sep 17 00:00:00 2001 From: Kenny Kerr Date: Fri, 17 Nov 2023 09:39:30 -0600 Subject: [PATCH 52/57] 52 --- Cargo.toml | 2 +- src/offset/local/win_bindings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d60e706f0..6211d9f835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for windows-targets = { version = "0.52", optional = true } [target.'cfg(windows)'.dev-dependencies] -windows-bindgen = { version = "0.51" } +windows-bindgen = { version = "0.52" } [target.'cfg(unix)'.dependencies] iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } diff --git a/src/offset/local/win_bindings.rs b/src/offset/local/win_bindings.rs index 292c951414..592726c732 100644 --- a/src/offset/local/win_bindings.rs +++ b/src/offset/local/win_bindings.rs @@ -1,4 +1,4 @@ -// Bindings generated by `windows-bindgen` 0.51.1 +// Bindings generated by `windows-bindgen` 0.52.0 #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] ::windows_targets::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); From b5381f8fb5c2ba32260288dc2fc43a711ed2549d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 25 Jan 2024 06:29:21 +0000 Subject: [PATCH 53/57] Fixed typo in Duration::hours() exception --- src/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duration.rs b/src/duration.rs index 63fa66fd49..c4e9ee2d62 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -116,7 +116,7 @@ impl Duration { #[inline] #[must_use] pub fn hours(hours: i64) -> Duration { - Duration::try_hours(hours).expect("Duration::hours ouf of bounds") + Duration::try_hours(hours).expect("Duration::hours out of bounds") } /// Makes a new `Duration` with given number of hours. From a73b54320ac24a770b2e451e60e84eb52a6acbf9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 17:08:37 +0100 Subject: [PATCH 54/57] Don't assume `rkyv-(16|32|64)` implies the `rkyv` feature --- src/datetime/mod.rs | 9 ++++++--- src/duration.rs | 6 +++--- src/lib.rs | 2 +- src/month.rs | 6 +++--- src/naive/date.rs | 6 +++--- src/naive/datetime/mod.rs | 6 +++--- src/naive/isoweek.rs | 6 +++--- src/naive/time/mod.rs | 6 +++--- src/offset/fixed.rs | 6 +++--- src/offset/local/mod.rs | 10 +++++++--- src/offset/utc.rs | 6 +++--- src/weekday.rs | 6 +++--- 12 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 6b3e49f5f4..e12b2db762 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -31,7 +31,7 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::Date; use crate::{Datelike, Months, Timelike, Weekday}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "rustc-serialize")] @@ -50,8 +50,11 @@ mod tests; /// the general-purpose constructors are all via the methods on the /// [`TimeZone`](./offset/trait.TimeZone.html) implementations. #[derive(Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive(compare(PartialEq, PartialOrd)))] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)) +)] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct DateTime { datetime: NaiveDateTime, diff --git a/src/duration.rs b/src/duration.rs index c4e9ee2d62..e4498b9f72 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -16,7 +16,7 @@ use core::{fmt, i64}; #[cfg(feature = "std")] use std::error::Error; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// The number of nanoseconds in a microsecond. @@ -51,9 +51,9 @@ macro_rules! try_opt { /// /// This also allows for the negative duration; see individual methods for details. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/lib.rs b/src/lib.rs index 086dbe450c..e532d7479f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -578,7 +578,7 @@ pub mod serde { /// Zero-copy serialization/deserialization with rkyv. /// /// This module re-exports the `Archived*` versions of chrono's types. -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] pub mod rkyv { pub use crate::datetime::ArchivedDateTime; pub use crate::duration::ArchivedDuration; diff --git a/src/month.rs b/src/month.rs index 1e1531d716..c3c3579d22 100644 --- a/src/month.rs +++ b/src/month.rs @@ -1,6 +1,6 @@ use core::fmt; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::OutOfRange; @@ -30,9 +30,9 @@ use crate::OutOfRange; // Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/date.rs b/src/naive/date.rs index 11952e2766..c8acb9efc6 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -9,7 +9,7 @@ use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// L10n locales. @@ -189,9 +189,9 @@ impl Days { /// /// [proleptic Gregorian date]: crate::NaiveDate#calendar-date #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 764a1743a8..ece1c80092 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -10,7 +10,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::{Duration as OldDuration, NANOS_PER_SEC}; @@ -74,9 +74,9 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; /// assert_eq!(dt.num_seconds_from_midnight(), 33011); /// ``` #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 8ef982701e..4b3d8d9d85 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -7,7 +7,7 @@ use core::fmt; use super::internals::{DateImpl, Of, YearFlags}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 week. @@ -17,9 +17,9 @@ use rkyv::{Archive, Deserialize, Serialize}; /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 9a2e5d8f76..94aee99168 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -9,7 +9,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::time::Duration; use core::{fmt, str}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::duration::Duration as OldDuration; @@ -203,9 +203,9 @@ mod tests; /// Since Chrono alone cannot determine any existence of leap seconds, /// **there is absolutely no guarantee that the leap second read has actually happened**. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq, PartialOrd)), archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) )] diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 1826ec4ec2..387a5cc0ab 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -6,7 +6,7 @@ use core::fmt; use core::str::FromStr; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; @@ -20,9 +20,9 @@ use crate::naive::{NaiveDate, NaiveDateTime}; /// `DateTime` instances. See the [`east_opt`](#method.east_opt) and /// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) )] diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index af1c21e7ef..1767763738 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -3,7 +3,7 @@ //! The local (system) time zone. -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; @@ -104,8 +104,12 @@ mod tz_info; /// assert!(dt1 >= dt2); /// ``` #[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] -#[cfg_attr(feature = "rkyv", archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, Debug)))] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, Debug)) +)] #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Local; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 7c23a282bb..e68dc227f3 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -14,7 +14,7 @@ use core::fmt; ))] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use super::{FixedOffset, LocalResult, Offset, TimeZone}; @@ -41,9 +41,9 @@ use crate::{Date, DateTime}; /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] diff --git a/src/weekday.rs b/src/weekday.rs index 786d516c7b..03a4b5a7fd 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -1,6 +1,6 @@ use core::fmt; -#[cfg(feature = "rkyv")] +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] use rkyv::{Archive, Deserialize, Serialize}; use crate::OutOfRange; @@ -31,9 +31,9 @@ use crate::OutOfRange; /// ``` #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr( - feature = "rkyv", + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) )] From a9b37c4c81de09b5b420b617ee6394f30ce491b1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 16:16:10 +0100 Subject: [PATCH 55/57] Make `rkyv` feature default to `size_32` --- Cargo.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6211d9f835..cf434b6a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,11 @@ oldtime = [] wasmbind = ["wasm-bindgen", "js-sys"] unstable-locales = ["pure-rust-locales"] # Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive. -rkyv-16 = ["rkyv", "rkyv?/size_16"] -rkyv-32 = ["rkyv", "rkyv?/size_32"] -rkyv-64 = ["rkyv", "rkyv?/size_64"] -rkyv-validation = ["rkyv", "rkyv?/validation"] +rkyv = ["dep:rkyv", "rkyv/size_32"] +rkyv-16 = ["dep:rkyv", "rkyv?/size_16"] +rkyv-32 = ["dep:rkyv", "rkyv?/size_32"] +rkyv-64 = ["dep:rkyv", "rkyv?/size_64"] +rkyv-validation = ["rkyv?/validation"] # Features for internal use only: __internal_bench = [] From 7c419a358e8978fa774e649c300196095365eb58 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 24 Jan 2024 21:44:10 +0100 Subject: [PATCH 56/57] Prepare 0.4.33 release --- CITATION.cff | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7546aff126..8ee34c2148 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ cff-version: 1.2.0 message: Please cite this crate using these information. # Version information. -date-released: 2024-01-22 -version: 0.4.32 +date-released: 2024-01-25 +version: 0.4.33 # Project information. abstract: Date and time library for Rust diff --git a/Cargo.toml b/Cargo.toml index cf434b6a86..8c8d8304c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.32" +version = "0.4.33" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From 94f9738b03a18c05226526da70191997a532b613 Mon Sep 17 00:00:00 2001 From: Eden Mikitas Date: Wed, 4 Oct 2023 09:54:21 +0300 Subject: [PATCH 57/57] Fix the formatting of `timestamp_micros`'s Example doc --- src/offset/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index ae3634e549..02ad1c7017 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -445,7 +445,8 @@ pub trait TimeZone: Sized + Clone { /// Makes a new `DateTime` from the number of non-leap microseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// #Example + /// # Example + /// /// ``` /// use chrono::{Utc, TimeZone}; ///