From 6bd2115c2b4855af227834475d971e6b5a0713c3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 11 Sep 2023 22:15:10 +0200 Subject: [PATCH 1/5] Change example for leap seconds not on the minute boundary --- src/naive/time/mod.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 43e614bab0..9fa4b8929a 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -171,22 +171,29 @@ mod tests; /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); /// ``` /// -/// There are hypothetical leap seconds not on the minute boundary -/// nevertheless supported by Chrono. -/// They are allowed for the sake of completeness and consistency; -/// there were several "exotic" time zone offsets with fractional minutes prior to UTC after all. -/// For such cases the human-readable representation is ambiguous -/// and would be read back to the next non-leap second. +/// There are hypothetical leap seconds not on the minute boundary nevertheless supported by Chrono. +/// They are allowed for the sake of completeness and consistency; there were several "exotic" time +/// zone offsets with fractional minutes prior to UTC after all. +/// For such cases the human-readable representation is ambiguous and would be read back to the next +/// non-leap second. /// /// ``` -/// use chrono::{DateTime, Utc, TimeZone, NaiveDate}; -/// -/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 56, 4, 1_000).unwrap().and_local_timezone(Utc).unwrap(); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// -/// let dt = Utc.with_ymd_and_hms(2015, 6, 30, 23, 56, 5).unwrap(); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); +/// use chrono::{FixedOffset, NaiveDate, TimeZone}; +/// +/// let paramaribo_pre1945 = FixedOffset::east_opt(-13236).unwrap(); // -03:40:36 +/// let leap_sec_2015 = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); +/// let dt1 = paramaribo_pre1945.from_utc_datetime(&leap_sec_2015); +/// assert_eq!(format!("{:?}", dt1), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt1.time()), "20:19:24"); +/// +/// let next_sec = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); +/// let dt2 = paramaribo_pre1945.from_utc_datetime(&next_sec); +/// assert_eq!(format!("{:?}", dt2), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt2.time()), "20:19:24"); +/// +/// assert!(dt1.time() != dt2.time()); +/// assert!(dt1.time().to_string() == dt2.time().to_string()); /// ``` /// /// Since Chrono alone cannot determine any existence of leap seconds, From bf007c7c7a759497869fae98b960a7fdce2d58e9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:25:35 +0200 Subject: [PATCH 2/5] Don't create strange leap seconds in tests --- src/naive/datetime/mod.rs | 6 ++-- src/naive/time/mod.rs | 6 ++-- src/naive/time/tests.rs | 62 +++++++++++++++++++-------------------- src/round.rs | 12 ++++---- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 09268aeffd..387f03207d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1443,11 +1443,11 @@ impl Timelike for NaiveDateTime { /// ``` /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 333_333_333).unwrap())); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 56, 1_333_333_333).unwrap())); + /// Some(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_nano_opt(12, 34, 59, 1_333_333_333).unwrap())); /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); /// ``` #[inline] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 9fa4b8929a..e9e2e02970 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1022,9 +1022,9 @@ impl Timelike for NaiveTime { /// /// ``` /// # use chrono::{NaiveTime, Timelike}; - /// # let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), - /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 1_333_333_333).unwrap())); + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// let strange_leap_second = dt.with_nanosecond(1_333_333_333).unwrap(); + /// assert_eq!(strange_leap_second.nanosecond(), 1_333_333_333); /// ``` #[inline] fn with_nanosecond(&self, nano: u32) -> Option { diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index b77ca87339..f7a6ef5f92 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -13,12 +13,12 @@ fn test_time_from_hms_milli() { Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap()) ); assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_000_000).unwrap()) + NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None); } #[test] @@ -36,12 +36,12 @@ fn test_time_from_hms_micro() { Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap()) ); assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 1_999_999_000).unwrap()) + NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap()) ); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None); } #[test] @@ -94,19 +94,19 @@ fn test_time_add() { let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); - check!(hmsm(3, 5, 7, 900), OldDuration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), OldDuration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), OldDuration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), OldDuration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), OldDuration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), OldDuration::days(-1), hmsm(3, 5, 8, 300)); + check!(hmsm(3, 5, 59, 900), OldDuration::zero(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 900), OldDuration::milliseconds(100), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-1800), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-800), hmsm(3, 5, 59, 500)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(-100), hmsm(3, 5, 59, 1_200)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(100), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(800), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::milliseconds(1800), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), OldDuration::seconds(86399), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), OldDuration::seconds(-86399), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), OldDuration::days(12345), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::days(1), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), OldDuration::days(-1), hmsm(3, 6, 0, 300)); // regression tests for #37 check!(hmsm(0, 0, 0, 0), OldDuration::milliseconds(-990), hmsm(23, 59, 59, 10)); @@ -132,12 +132,12 @@ fn test_time_overflowing_add() { // overflowing_add_signed with leap seconds may be counter-intuitive assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(1)), - (hmsm(3, 4, 5, 678), 86_400) + hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(1)), + (hmsm(3, 4, 59, 678), 86_400) ); assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(OldDuration::days(-1)), - (hmsm(3, 4, 6, 678), -86_400) + hmsm(3, 4, 59, 1_678).overflowing_add_signed(OldDuration::days(-1)), + (hmsm(3, 5, 0, 678), -86_400) ); } @@ -184,14 +184,14 @@ fn test_time_sub() { // treats the leap second as if it coincides with the prior non-leap second, // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), OldDuration::milliseconds(400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), OldDuration::milliseconds(1400)); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), OldDuration::milliseconds(1400)); // additional equality: `time1 + duration = time2` is equivalent to // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. assert_eq!(hmsm(3, 5, 6, 800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + OldDuration::milliseconds(400), hmsm(3, 5, 7, 200)); } #[test] diff --git a/src/round.rs b/src/round.rs index 9271353b77..3cba01d4c5 100644 --- a/src/round.rs +++ b/src/round.rs @@ -769,16 +769,16 @@ mod tests { #[test] fn issue1010() { - let dt = NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288).unwrap(); - let span = Duration::microseconds(-7019067213869040); + let dt = NaiveDateTime::from_timestamp_opt(-4_227_854_320, 678_774_288).unwrap(); + let span = Duration::microseconds(-7_019_067_213_869_040); assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(320041586, 1920103021).unwrap(); - let span = Duration::nanoseconds(-8923838508697114584); + let dt = NaiveDateTime::from_timestamp_opt(320_041_586, 920_103_021).unwrap(); + let span = Duration::nanoseconds(-8_923_838_508_697_114_584); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); - let dt = NaiveDateTime::from_timestamp_opt(-2621440, 0).unwrap(); - let span = Duration::nanoseconds(-9223372036854771421); + let dt = NaiveDateTime::from_timestamp_opt(-2_621_440, 0).unwrap(); + let span = Duration::nanoseconds(-9_223_372_036_854_771_421); assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); } } From 33267294cb2daf92287b188ca2b426a6537293af Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 17:07:34 +0200 Subject: [PATCH 3/5] Don't generate leap seconds that are not 60 in NaiveTime's Arbitrary impl --- src/naive/time/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index e9e2e02970..88e9d50ce8 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -208,9 +208,14 @@ pub struct NaiveTime { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for NaiveTime { fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { - let secs = u.int_in_range(0..=86_399)?; - let nano = u.int_in_range(0..=1_999_999_999)?; - let time = NaiveTime::from_num_seconds_from_midnight_opt(secs, nano) + let mins = u.int_in_range(0..=1439)?; + let mut secs = u.int_in_range(0..=60)?; + let mut nano = u.int_in_range(0..=999_999_999)?; + if secs == 60 { + secs = 59; + nano += 1_000_000_000; + } + let time = NaiveTime::from_num_seconds_from_midnight_opt(mins * 60 + secs, nano) .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); Ok(time) } From f83bafa46ad8838d5b249ed3848b8a8eca443399 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:31:00 +0200 Subject: [PATCH 4/5] Deny leap second if secs != 59 in `from_hms_nano_opt` --- src/naive/date.rs | 24 ++++++++++++------------ src/naive/time/mod.rs | 33 ++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 860593498d..a642dbdc0a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -861,8 +861,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -876,8 +876,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -911,8 +911,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -940,8 +940,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -975,8 +975,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -990,8 +990,8 @@ impl NaiveDate { /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 88e9d50ce8..72644e2647 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -177,6 +177,10 @@ mod tests; /// For such cases the human-readable representation is ambiguous and would be read back to the next /// non-leap second. /// +/// A `NaiveTime` with a leap second that is not on a minute boundary can only be created from a +/// [`DateTime`](crate::DateTime) with fractional minutes as offset, or using +/// [`Timelike::with_nanosecond()`]. +/// /// ``` /// use chrono::{FixedOffset, NaiveDate, TimeZone}; /// @@ -239,8 +243,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute and second. /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead. + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -282,8 +286,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and millisecond. /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -318,8 +322,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -333,8 +337,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and microsecond. /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -369,8 +373,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Panics /// @@ -384,8 +388,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. /// /// # Errors /// @@ -409,7 +413,10 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { - if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { + if (hour >= 24 || min >= 60 || sec >= 60) + || (nano >= 1_000_000_000 && sec != 59) + || nano >= 2_000_000_000 + { return None; } let secs = hour * 3600 + min * 60 + sec; From 8a64c8fd606b04f291c881a380e7a55833f5be96 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 12 Sep 2023 08:36:52 +0200 Subject: [PATCH 5/5] Deny leap second if secs != 59 in `from_num_seconds_from_midnight_opt` --- src/datetime/mod.rs | 12 +++++++++--- src/naive/datetime/mod.rs | 15 ++++++++------- src/naive/time/mod.rs | 10 +++++----- src/offset/mod.rs | 12 ++++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 78c3974ccb..3dfe8a1e30 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -579,12 +579,18 @@ impl DateTime { /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp) and /// [`timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos). /// - /// Returns `None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. - /// /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]. /// + /// 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`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. + /// /// # Example /// /// ``` diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 387f03207d..32367e0372 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -110,9 +110,9 @@ impl NaiveDateTime { /// For a non-naive version of this function see /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). /// - /// The nanosecond part can exceed 1,000,000,000 in order to represent the - /// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX - /// timestamp" cannot represent a leap second unambiguously.) + /// 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`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Panics /// @@ -196,8 +196,8 @@ 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. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// 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`. /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// /// # Errors @@ -216,8 +216,9 @@ impl NaiveDateTime { /// /// assert!(from_timestamp_opt(0, 0).is_some()); /// assert!(from_timestamp_opt(0, 999_999_999).is_some()); - /// assert!(from_timestamp_opt(0, 1_500_000_000).is_some()); // leap second - /// assert!(from_timestamp_opt(0, 2_000_000_000).is_none()); + /// assert!(from_timestamp_opt(0, 1_500_000_000).is_none()); // invalid leap second + /// assert!(from_timestamp_opt(59, 1_500_000_000).is_some()); // leap second + /// assert!(from_timestamp_opt(59, 2_000_000_000).is_none()); /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); /// ``` #[inline] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 72644e2647..da35a49644 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -425,8 +425,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Panics /// @@ -440,8 +440,8 @@ impl NaiveTime { /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. /// /// # Errors /// @@ -463,7 +463,7 @@ impl NaiveTime { #[inline] #[must_use] pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { - if secs >= 86_400 || nano >= 2_000_000_000 { + if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { return None; } Some(NaiveTime { secs, frac: nano }) diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 419550e292..7f5857b234 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -347,6 +347,12 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// + /// 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`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Panics + /// /// Panics on the out-of-range number of seconds and/or invalid nanosecond, /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] @@ -358,6 +364,12 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// + /// 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`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// /// Returns `LocalResult::None` on out-of-range number of seconds and/or /// invalid nanosecond, otherwise always returns `LocalResult::Single`. ///