Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't allow strange leap seconds in initialization methods #1283

Merged
merged 5 commits into from Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/datetime/mod.rs
Expand Up @@ -579,12 +579,18 @@ impl DateTime<Utc> {
/// 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
///
/// ```
Expand Down
24 changes: 12 additions & 12 deletions src/naive/date.rs
Expand Up @@ -861,8 +861,8 @@

/// 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
///
Expand All @@ -876,8 +876,8 @@

/// 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`.

Check warning on line 880 in src/naive/date.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/date.rs#L879-L880

Added lines #L879 - L880 were not covered by tests
///
/// # Errors
///
Expand Down Expand Up @@ -911,8 +911,8 @@

/// 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
///
Expand Down Expand Up @@ -940,8 +940,8 @@

/// 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`.

Check warning on line 944 in src/naive/date.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/date.rs#L943-L944

Added lines #L943 - L944 were not covered by tests
///
/// # Errors
///
Expand Down Expand Up @@ -975,8 +975,8 @@

/// 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
///
Expand All @@ -990,8 +990,8 @@

/// 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`.

Check warning on line 994 in src/naive/date.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/date.rs#L993-L994

Added lines #L993 - L994 were not covered by tests
///
/// # Errors
///
Expand Down
21 changes: 11 additions & 10 deletions src/naive/datetime/mod.rs
Expand Up @@ -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
///
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -1443,11 +1444,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]
Expand Down
95 changes: 57 additions & 38 deletions src/naive/time/mod.rs
Expand Up @@ -171,22 +171,33 @@
/// 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");
/// 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()`].
///
/// 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,
Expand All @@ -201,9 +212,14 @@
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for NaiveTime {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<NaiveTime> {
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)

Check warning on line 222 in src/naive/time/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/time/mod.rs#L215-L222

Added lines #L215 - L222 were not covered by tests
.expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous.");
Ok(time)
}
Expand All @@ -227,8 +243,8 @@

/// 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
///
Expand Down Expand Up @@ -270,8 +286,8 @@

/// 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
///
Expand Down Expand Up @@ -306,8 +322,8 @@

/// 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
///
Expand All @@ -321,8 +337,8 @@

/// 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
///
Expand Down Expand Up @@ -357,8 +373,8 @@

/// 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
///
Expand All @@ -372,8 +388,8 @@

/// 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
///
Expand All @@ -397,7 +413,10 @@
#[inline]
#[must_use]
pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option<NaiveTime> {
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;
Expand All @@ -406,8 +425,8 @@

/// 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
///
Expand All @@ -421,8 +440,8 @@

/// 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
///
Expand All @@ -444,7 +463,7 @@
#[inline]
#[must_use]
pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option<NaiveTime> {
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 })
Expand Down Expand Up @@ -1015,9 +1034,9 @@
///
/// ```
/// # 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<NaiveTime> {
Expand Down