From 366e7ea0c56c5a29d4bd8557ef3fa4dd4b272b6f Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:29:57 -0500 Subject: [PATCH 01/18] Add sign requirements for years to docs (cherry picked from commit 7dfd8ca8e6b11c79a9035ba3b3ec03117e7999a9) --- src/format/strftime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 6338194366..aafe01f736 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -11,7 +11,7 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).| | `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | | `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | From 64c4c0149d2765f401e060656a20d8b86a60ea6a Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 09:59:17 -0500 Subject: [PATCH 02/18] Add (+/-) for consistency with strftime.rs (cherry picked from commit a2fb1212f383e6ab59c0dec7854c173e8e63ef34) --- src/format/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index cfbc54ab7c..e9d2d2f9c3 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -99,7 +99,7 @@ pub enum Pad { #[derive(Clone, PartialEq, Eq, Debug)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). - /// May accept years before 1 BCE or after 9999 CE, given an initial sign. + /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). Year, /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year. YearDiv100, From e63b7297c7793f73449ec3053ee26b976656fe4d Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 30 Jan 2023 10:17:47 -0500 Subject: [PATCH 03/18] Add example of date conversion after 9999 CE (cherry picked from commit 05b865deffff478facd737a540aa62d1f061acb6) --- src/naive/datetime/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c34813f10b..68a1be20e9 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -308,6 +308,16 @@ impl NaiveDateTime { /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); /// ``` + /// + /// Years before 1 BCE or after 9999 CE, require an initial sign + /// + ///``` + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S"; + /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); + /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); + ///``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; From cc64f84ac85a3927c7be495f1e1dcf24e6399355 Mon Sep 17 00:00:00 2001 From: Bruce Guenter Date: Thu, 20 Oct 2022 11:47:02 -0600 Subject: [PATCH 04/18] derive `Hash` for most pub types that also derive `PartialEq` --- src/datetime/mod.rs | 2 +- src/format/mod.rs | 35 +++++++++++------------------------ src/format/parsed.rs | 2 +- src/naive/internals.rs | 2 +- src/naive/isoweek.rs | 2 +- src/offset/utc.rs | 2 +- src/oldtime.rs | 2 +- 7 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 00450ce9c7..38416cb6fa 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -52,7 +52,7 @@ mod tests; /// 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)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, diff --git a/src/format/mod.rs b/src/format/mod.rs index e9d2d2f9c3..d5c4212a40 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -69,11 +69,11 @@ pub use strftime::StrftimeItems; struct Locale; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] enum Void {} /// Padding characters for numeric items. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Pad { /// No padding. None, @@ -96,7 +96,7 @@ pub enum Pad { /// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). @@ -152,24 +152,11 @@ pub enum Numeric { } /// An opaque type representing numeric item types for internal uses only. +#[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } -impl Clone for InternalNumeric { - fn clone(&self) -> Self { - match self._dummy {} - } -} - -impl PartialEq for InternalNumeric { - fn eq(&self, _other: &InternalNumeric) -> bool { - match self._dummy {} - } -} - -impl Eq for InternalNumeric {} - impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") @@ -180,7 +167,7 @@ impl fmt::Debug for InternalNumeric { /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. /// @@ -264,12 +251,12 @@ pub enum Fixed { } /// An opaque type representing fixed-format item types for internal uses only. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InternalFixed { val: InternalInternal, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -289,7 +276,7 @@ enum InternalInternal { } #[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Colons { None, Single, @@ -298,7 +285,7 @@ enum Colons { } /// A single formatting item. This is used for both formatting and parsing. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), @@ -358,7 +345,7 @@ macro_rules! internal_fix { } /// An error from the `parse` function. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ParseError(ParseErrorKind); impl ParseError { @@ -369,7 +356,7 @@ impl ParseError { } /// The category of parse error -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 54679ccda1..fc4c4540bd 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -22,7 +22,7 @@ use crate::{Datelike, Timelike}; /// /// - `to_*` methods try to make a concrete date and time value out of set fields. /// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[derive(Clone, PartialEq, Eq, Debug, Default)] +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { /// Year. /// diff --git a/src/naive/internals.rs b/src/naive/internals.rs index 1b113d51d3..cd87973712 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -35,7 +35,7 @@ pub(super) const MIN_YEAR: DateImpl = i32::MIN >> 13; /// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year /// (simplifies the day of week calculation from the 1-based ordinal). #[allow(unreachable_pub)] // public as an alias for benchmarks only -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, Hash)] pub struct YearFlags(pub(super) u8); pub(super) const A: YearFlags = YearFlags(0o15); diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 109535f547..501b08c769 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; /// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). /// 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)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct IsoWeek { // note that this allows for larger year range than `NaiveDate`. diff --git a/src/offset/utc.rs b/src/offset/utc.rs index ee3e70a0bc..cfed754b2f 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -40,7 +40,7 @@ use crate::{Date, DateTime}; /// assert_eq!(Utc.timestamp(61, 0), dt); /// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); /// ``` -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Utc; diff --git a/src/oldtime.rs b/src/oldtime.rs index f935e9a4b1..f604524899 100644 --- a/src/oldtime.rs +++ b/src/oldtime.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)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Duration { secs: i64, From 90c7e55738cab4fe9bb946ce2a0a913cf2677c9c Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 13:33:19 +0100 Subject: [PATCH 05/18] Run CI on changes to any file Currently CI is only run on changes to specific files. The list of these files incomplete and would likely get outdated again if it were expanded. Exluding specific files would be a shorter, more stable list. However, the changelog is only kept for posterity and the readme has seen all of two commits in the past three years that wouldn't have triggered CI by changing other files as well, so don't bother and just run CI for any changed file. --- .github/workflows/lint.yml | 6 ------ .github/workflows/test.yml | 5 ----- 2 files changed, 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e299586ef8..dc3394c87c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,12 +4,6 @@ on: push: branches: [main, 0.4.x] pull_request: - paths: - - "**.rs" - - .github/** - - .ci/** - - Cargo.toml - - deny.toml jobs: lint: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63e7c81c5c..ab968aac2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,11 +4,6 @@ on: push: branches: [main, 0.4.x] pull_request: - paths: - - "**.rs" - - .github/** - - .ci/** - - Cargo.toml jobs: timezones_linux: From d1043f39c8901bba8cf89081dcb2959aec0ddcfb Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:31:13 +0100 Subject: [PATCH 06/18] Add chrono-fuzz to CI Ensures future changes to break it again. --- .github/workflows/lint.yml | 8 ++++++-- .github/workflows/test.yml | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dc3394c87c..41145a30a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,8 +13,12 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v1 - - run: cargo fmt -- --check --color=always - - run: cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + - run: | + cargo fmt --check -- --color=always + cargo fmt --check --manifest-path fuzz/Cargo.toml + - run: | + cargo clippy --color=always -- -D warnings -A clippy::manual-non-exhaustive + cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings env: RUSTFLAGS: "-Dwarnings" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab968aac2f..851ef5cec7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,7 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v1 + - 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 From cd0e3b008c1ea5c6835709ff72a1b76209f3831b Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:36:30 +0100 Subject: [PATCH 07/18] chrono-fuzz: Update libfuzzer-sys dependency from 0.3 to 0.4 --- fuzz/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bcbc3aad25..d15c9a9f7f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" +libfuzzer-sys = "0.4" [dependencies.chrono] path = ".." From f9f3c7857d31c1c2341a1ddee0e9447f81a61465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Gaspard?= Date: Sun, 22 Jan 2023 20:23:50 +0100 Subject: [PATCH 08/18] Fix panic in DateTime::checked_add_days This is a backport of #941, except it needs to work around the fact that we can't modify the `time` crate. --- src/naive/date.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 3734460713..79d0a98baa 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -646,6 +646,10 @@ impl NaiveDate { /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(1000000000000)), + /// None + /// ); /// ``` pub fn checked_add_days(self, days: Days) -> Option { if days.0 == 0 { @@ -665,6 +669,10 @@ impl NaiveDate { /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)), + /// None + /// ); /// ``` pub fn checked_sub_days(self, days: Days) -> Option { if days.0 == 0 { @@ -675,7 +683,11 @@ impl NaiveDate { } fn diff_days(self, days: i64) -> Option { - self.checked_add_signed(Duration::days(days)) + let secs = days.checked_mul(86400)?; // 86400 seconds in one day + if secs >= core::i64::MAX / 1000 || secs <= core::i64::MIN / 1000 { + return None; // See the `time` 0.1 crate. Outside these bounds, `Duration::seconds` will panic + } + self.checked_add_signed(Duration::seconds(secs)) } /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. From a9b1ec412a6224020a2e9664a45974dcf71fdace Mon Sep 17 00:00:00 2001 From: raphaelroosz <121112446+raphaelroosz@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:31:56 +0100 Subject: [PATCH 09/18] fix ordinal week calculation * math is 0 based while ordinal is 1 based => fix as 1 based logic * add extensive testing against the "date" command format * format: test sample instead of every day * 2007 starts with saturday * Last day of the year is thus the 52 on Monday weekly calendar, 53 on Sunday weekly calendar. * update %U expected value in test * Was the goal was to have a different value than with %W at next line ? another date to pick ? * update cfg("unix") into cfg(target_os = "linux") * format tests/dateutils.rs --- src/format/mod.rs | 4 +-- src/format/strftime.rs | 2 +- src/naive/date.rs | 2 +- tests/dateutils.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d5c4212a40..e3b9b51344 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -521,10 +521,10 @@ fn format_inner( use self::Numeric::*; let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7 + (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 6) / 7 }; let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7 + (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 6) / 7 }; let (width, v) = match *spec { diff --git a/src/format/strftime.rs b/src/format/strftime.rs index aafe01f736..dcaabe49f2 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -584,7 +584,7 @@ fn test_strftime_docs() { assert_eq!(dt.format("%A").to_string(), "Sunday"); assert_eq!(dt.format("%w").to_string(), "0"); assert_eq!(dt.format("%u").to_string(), "7"); - assert_eq!(dt.format("%U").to_string(), "28"); + assert_eq!(dt.format("%U").to_string(), "27"); assert_eq!(dt.format("%W").to_string(), "27"); assert_eq!(dt.format("%G").to_string(), "2001"); assert_eq!(dt.format("%g").to_string(), "01"); diff --git a/src/naive/date.rs b/src/naive/date.rs index 79d0a98baa..e3cc9b6c6a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2869,7 +2869,7 @@ mod tests { // corner cases assert_eq!( NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), - "2008,08,53,53,01" + "2008,08,52,53,01" ); assert_eq!( NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 130649a71f..dec6bfe117 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -74,3 +74,58 @@ fn try_verify_against_date_command() { date += chrono::Duration::hours(1); } } + +#[cfg(target_os = "linux")] +fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) { + let required_format = + "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; + // a%a - depends from localization + // A%A - depends from localization + // b%b - depends from localization + // B%B - depends from localization + // h%h - depends from localization + // c%c - depends from localization + // p%p - depends from localization + // r%r - depends from localization + // x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D + // Z%Z - too many ways to represent it, will most likely fail + + let output = process::Command::new(path) + .arg("-d") + .arg(format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + )) + .arg(format!("+{}", required_format)) + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + let ldt = Local + .from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap()) + .unwrap(); + let formated_date = format!("{}\n", ldt.format(required_format)); + assert_eq!(date_command_str, formated_date); +} + +#[test] +#[cfg(target_os = "linux")] +fn try_verify_against_date_command_format() { + let date_path = "/usr/bin/date"; + + if !path::Path::new(date_path).exists() { + // date command not found, skipping + return; + } + let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); + while date.year() < 2008 { + verify_against_date_command_format_local(date_path, date); + date += chrono::Duration::days(55); + } +} From 8197700ccdbb77a355e47ef8f4d9720ef41ff564 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Tue, 14 Feb 2023 11:41:14 +0000 Subject: [PATCH 10/18] apply same fix to parsing and add failing test cases as per issue #961 --- src/format/parsed.rs | 4 ++-- src/naive/date.rs | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/format/parsed.rs b/src/format/parsed.rs index fc4c4540bd..eb697e12bf 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -380,8 +380,8 @@ impl Parsed { let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 7) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 7) / 7; + let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 6) / 7; + let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 6) / 7; self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon diff --git a/src/naive/date.rs b/src/naive/date.rs index e3cc9b6c6a..eccbcf2ff4 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2831,6 +2831,16 @@ mod tests { assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); } #[test] From cf2a2f95f7030860b0eda2b78eff968f1d7b4228 Mon Sep 17 00:00:00 2001 From: Eric Sheppard Date: Thu, 16 Feb 2023 12:33:39 +0000 Subject: [PATCH 11/18] factor calculations to weeks_from function and add tests tests for weeks_from and num_days_from fix array iter MSRV issue --- src/format/mod.rs | 8 +--- src/format/parsed.rs | 5 +-- src/naive/date.rs | 66 ++++++++++++++++++++++++++++++++ src/weekday.rs | 89 ++++++++++++++++++++++++++------------------ 4 files changed, 123 insertions(+), 45 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index e3b9b51344..c6b8eaee09 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -520,12 +520,8 @@ fn format_inner( Item::Numeric(ref spec, ref pad) => { use self::Numeric::*; - let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 6) / 7 - }; - let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 6) / 7 - }; + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index eb697e12bf..6cc29e9d40 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -379,9 +379,8 @@ impl Parsed { // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); - let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 6) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 6) / 7; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon diff --git a/src/naive/date.rs b/src/naive/date.rs index eccbcf2ff4..46c2092513 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -236,6 +236,9 @@ fn test_date_bounds() { } impl NaiveDate { + pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { + (self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7 + } /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. fn from_of(year: i32, of: Of) -> Option { if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() { @@ -2928,4 +2931,67 @@ mod tests { assert!(days.contains(&date)); } } + + #[test] + fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!([52, 53] + .contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)),); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } + } } diff --git a/src/weekday.rs b/src/weekday.rs index bd30e1934d..c12dd01c64 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -73,15 +73,7 @@ impl Weekday { /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } + self.num_days_from(Weekday::Mon) + 1 } /// Returns a day-of-week number starting from Sunday = 1. @@ -91,15 +83,7 @@ impl Weekday { /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } + self.num_days_from(Weekday::Sun) + 1 } /// Returns a day-of-week number starting from Monday = 0. @@ -109,15 +93,7 @@ impl Weekday { /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } + self.num_days_from(Weekday::Mon) } /// Returns a day-of-week number starting from Sunday = 0. @@ -127,15 +103,17 @@ impl Weekday { /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } + self.num_days_from(Weekday::Sun) + } + + /// Returns a day-of-week number starting from the parameter `day` (D) = 0. + /// + /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + #[inline] + pub(crate) fn num_days_from(&self, day: Weekday) -> u32 { + (*self as u32 + 7 - day as u32) % 7 } } @@ -208,6 +186,45 @@ impl fmt::Debug for ParseWeekdayError { } } +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::Weekday; + + #[test] + fn test_num_days_from() { + for i in 0..7 { + let base_day = Weekday::from_u64(i).unwrap(); + + assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun)); + + assert_eq!(base_day.num_days_from(base_day), 0); + + assert_eq!(base_day.num_days_from(base_day.pred()), 1); + assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!( + base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()), + 6 + ); + + assert_eq!(base_day.num_days_from(base_day.succ()), 6); + assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!( + base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()), + 1 + ); + } + } +} + // the actual `FromStr` implementation is in the `format` module to leverage the existing code #[cfg(feature = "serde")] From fb2f2596f56b690ddd5fc5e473fc8380418802a8 Mon Sep 17 00:00:00 2001 From: Arthur Carcano Date: Tue, 7 Mar 2023 14:14:36 +0100 Subject: [PATCH 12/18] Make iana-time-zone a target specific dependency Currently, iana-tiime-zone is only used on cfg(unix). This crate, and its windows code in particular, contains a lot of unsafe, so it seems prudent to limit its scope to where it is actually needed. --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1a5c0d9d2b..14ae4d0322 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ serde = { version = "1.0.99", default-features = false, optional = true } pure-rust-locales = { version = "0.5.2", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = {version = "0.7", optional = true} -iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] @@ -47,6 +46,9 @@ js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.0", features = ["std", "minwinbase", "minwindef", "timezoneapi"], optional = true } +[target.'cfg(unix)'.dependencies] +iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] } + [dev-dependencies] serde_json = { version = "1" } serde_derive = { version = "1", default-features = false } From f5c5ac452dc7095c6acec12a7e5278194132bc06 Mon Sep 17 00:00:00 2001 From: Tormod Gjeitnes Hellen Date: Wed, 8 Mar 2023 18:23:56 +0100 Subject: [PATCH 13/18] Make eligible functions const. --- src/format/mod.rs | 2 +- src/month.rs | 2 +- src/naive/date.rs | 12 ++++---- src/naive/datetime/mod.rs | 6 ++-- src/naive/internals.rs | 14 +++++----- src/naive/isoweek.rs | 6 ++-- src/offset/fixed.rs | 4 +-- src/offset/local/tz_info/parser.rs | 4 +-- src/offset/local/tz_info/timezone.rs | 12 ++++---- src/oldtime.rs | 42 ++++++++++++++-------------- src/weekday.rs | 10 +++---- 11 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index c6b8eaee09..c05ba4d044 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -350,7 +350,7 @@ pub struct ParseError(ParseErrorKind); impl ParseError { /// The category of parse error - pub fn kind(&self) -> ParseErrorKind { + pub const fn kind(&self) -> ParseErrorKind { self.0 } } diff --git a/src/month.rs b/src/month.rs index f444dc0270..46f09d0fb8 100644 --- a/src/month.rs +++ b/src/month.rs @@ -197,7 +197,7 @@ pub struct Months(pub(crate) u32); impl Months { /// Construct a new `Months` from a number of months - pub fn new(num: u32) -> Self { + pub const fn new(num: u32) -> Self { Self(num) } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 46c2092513..64af978f3d 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -131,7 +131,7 @@ pub struct Days(pub(crate) u64); impl Days { /// Construct a new `Days` from a number of days - pub fn new(num: u64) -> Self { + pub const fn new(num: u64) -> Self { Self(num) } } @@ -708,7 +708,7 @@ impl NaiveDate { /// assert_eq!(dt.time(), t); /// ``` #[inline] - pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime { + pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { NaiveDateTime::new(*self, time) } @@ -898,7 +898,7 @@ impl NaiveDate { /// Returns the packed ordinal-flags. #[inline] - fn of(&self) -> Of { + const fn of(&self) -> Of { Of((self.ymdf & 0b1_1111_1111_1111) as u32) } @@ -1221,7 +1221,7 @@ impl NaiveDate { /// } /// ``` #[inline] - pub fn iter_days(&self) -> NaiveDateDaysIterator { + pub const fn iter_days(&self) -> NaiveDateDaysIterator { NaiveDateDaysIterator { value: *self } } @@ -1252,14 +1252,14 @@ impl NaiveDate { /// } /// ``` #[inline] - pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { + pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator { NaiveDateWeeksIterator { value: *self } } /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] /// specified. #[inline] - pub fn week(&self, start: Weekday) -> NaiveWeek { + pub const fn week(&self, start: Weekday) -> NaiveWeek { NaiveWeek { date: *self, start } } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 68a1be20e9..ec0d842c06 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -131,7 +131,7 @@ impl NaiveDateTime { /// assert_eq!(dt.time(), t); /// ``` #[inline] - pub fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { + pub const fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { NaiveDateTime { date, time } } @@ -335,7 +335,7 @@ impl NaiveDateTime { /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); /// ``` #[inline] - pub fn date(&self) -> NaiveDate { + pub const fn date(&self) -> NaiveDate { self.date } @@ -350,7 +350,7 @@ impl NaiveDateTime { /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); /// ``` #[inline] - pub fn time(&self) -> NaiveTime { + pub const fn time(&self) -> NaiveTime { self.time } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index cd87973712..05305b506f 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -141,7 +141,7 @@ impl YearFlags { } #[inline] - pub(super) fn nisoweeks(&self) -> u32 { + pub(super) const fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } @@ -294,7 +294,7 @@ impl Of { } #[inline] - pub(super) fn ordinal(&self) -> u32 { + pub(super) const fn ordinal(&self) -> u32 { let Of(of) = *self; of >> 4 } @@ -310,7 +310,7 @@ impl Of { } #[inline] - pub(super) fn flags(&self) -> YearFlags { + pub(super) const fn flags(&self) -> YearFlags { let Of(of) = *self; YearFlags((of & 0b1111) as u8) } @@ -336,13 +336,13 @@ impl Of { } #[inline] - pub(super) fn succ(&self) -> Of { + pub(super) const fn succ(&self) -> Of { let Of(of) = *self; Of(of + (1 << 4)) } #[inline] - pub(super) fn pred(&self) -> Of { + pub(super) const fn pred(&self) -> Of { let Of(of) = *self; Of(of - (1 << 4)) } @@ -398,7 +398,7 @@ impl Mdf { } #[inline] - pub(super) fn month(&self) -> u32 { + pub(super) const fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } @@ -414,7 +414,7 @@ impl Mdf { } #[inline] - pub(super) fn day(&self) -> u32 { + pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 501b08c769..6a4fcfd110 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -71,7 +71,7 @@ impl IsoWeek { /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); /// ``` #[inline] - pub fn year(&self) -> i32 { + pub const fn year(&self) -> i32 { self.ywf >> 10 } @@ -88,7 +88,7 @@ impl IsoWeek { /// assert_eq!(d.iso_week().week(), 15); /// ``` #[inline] - pub fn week(&self) -> u32 { + pub const fn week(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 } @@ -105,7 +105,7 @@ impl IsoWeek { /// assert_eq!(d.iso_week().week0(), 14); /// ``` #[inline] - pub fn week0(&self) -> u32 { + pub const fn week0(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 - 1 } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 4d336b9db6..0989dfa5ba 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -93,13 +93,13 @@ impl FixedOffset { /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] - pub fn local_minus_utc(&self) -> i32 { + pub const fn local_minus_utc(&self) -> i32 { self.local_minus_utc } /// Returns the number of seconds to add to convert from the local time to UTC. #[inline] - pub fn utc_minus_local(&self) -> i32 { + pub const fn utc_minus_local(&self) -> i32 { -self.local_minus_utc } } diff --git a/src/offset/local/tz_info/parser.rs b/src/offset/local/tz_info/parser.rs index 77f8e481b5..5652a0ea95 100644 --- a/src/offset/local/tz_info/parser.rs +++ b/src/offset/local/tz_info/parser.rs @@ -224,7 +224,7 @@ pub(crate) struct Cursor<'a> { impl<'a> Cursor<'a> { /// Construct a new `Cursor` from remaining data - pub(crate) fn new(remaining: &'a [u8]) -> Self { + pub(crate) const fn new(remaining: &'a [u8]) -> Self { Self { remaining, read_count: 0 } } @@ -233,7 +233,7 @@ impl<'a> Cursor<'a> { } /// Returns remaining data - pub(crate) fn remaining(&self) -> &'a [u8] { + pub(crate) const fn remaining(&self) -> &'a [u8] { self.remaining } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index ae6fb5f848..8572825a89 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -438,12 +438,12 @@ pub(super) struct Transition { impl Transition { /// Construct a TZif file transition - pub(super) fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { + pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { Self { unix_leap_time, local_time_type_index } } /// Returns Unix leap time - fn unix_leap_time(&self) -> i64 { + const fn unix_leap_time(&self) -> i64 { self.unix_leap_time } } @@ -459,12 +459,12 @@ pub(super) struct LeapSecond { impl LeapSecond { /// Construct a TZif file leap second - pub(super) fn new(unix_leap_time: i64, correction: i32) -> Self { + pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self { Self { unix_leap_time, correction } } /// Returns Unix leap time - fn unix_leap_time(&self) -> i64 { + const fn unix_leap_time(&self) -> i64 { self.unix_leap_time } } @@ -572,12 +572,12 @@ impl LocalTimeType { } /// Returns offset from UTC in seconds - pub(crate) fn offset(&self) -> i32 { + pub(crate) const fn offset(&self) -> i32 { self.ut_offset } /// Returns daylight saving time indicator - pub(super) fn is_dst(&self) -> bool { + pub(super) const fn is_dst(&self) -> bool { self.is_dst } diff --git a/src/oldtime.rs b/src/oldtime.rs index f604524899..8e2b3d2c09 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -120,7 +120,7 @@ impl Duration { /// Makes a new `Duration` with given number of milliseconds. #[inline] - pub fn milliseconds(milliseconds: i64) -> Duration { + pub const fn milliseconds(milliseconds: i64) -> Duration { let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); let nanos = millis as i32 * NANOS_PER_MILLI; Duration { secs: secs, nanos: nanos } @@ -128,7 +128,7 @@ impl Duration { /// Makes a new `Duration` with given number of microseconds. #[inline] - pub fn microseconds(microseconds: i64) -> Duration { + pub const fn microseconds(microseconds: i64) -> Duration { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); let nanos = micros as i32 * NANOS_PER_MICRO; Duration { secs: secs, nanos: nanos } @@ -136,36 +136,36 @@ impl Duration { /// Makes a new `Duration` with given number of nanoseconds. #[inline] - pub fn nanoseconds(nanos: i64) -> Duration { + pub const fn nanoseconds(nanos: i64) -> Duration { let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); Duration { secs: secs, nanos: nanos as i32 } } /// Returns the total number of whole weeks in the duration. #[inline] - pub fn num_weeks(&self) -> i64 { + pub const fn num_weeks(&self) -> i64 { self.num_days() / 7 } /// Returns the total number of whole days in the duration. - pub fn num_days(&self) -> i64 { + pub const fn num_days(&self) -> i64 { self.num_seconds() / SECS_PER_DAY } /// Returns the total number of whole hours in the duration. #[inline] - pub fn num_hours(&self) -> i64 { + pub const fn num_hours(&self) -> i64 { self.num_seconds() / SECS_PER_HOUR } /// Returns the total number of whole minutes in the duration. #[inline] - pub fn num_minutes(&self) -> i64 { + pub const fn num_minutes(&self) -> i64 { self.num_seconds() / SECS_PER_MINUTE } /// Returns the total number of whole seconds in the duration. - pub fn num_seconds(&self) -> i64 { + pub const fn num_seconds(&self) -> i64 { // If secs is negative, nanos should be subtracted from the duration. if self.secs < 0 && self.nanos > 0 { self.secs + 1 @@ -177,7 +177,7 @@ impl Duration { /// Returns the number of nanoseconds such that /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of /// nanoseconds in the duration. - fn nanos_mod_sec(&self) -> i32 { + const fn nanos_mod_sec(&self) -> i32 { if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { @@ -186,7 +186,7 @@ impl Duration { } /// Returns the total number of whole milliseconds in the duration, - pub fn num_milliseconds(&self) -> i64 { + pub const fn num_milliseconds(&self) -> i64 { // 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; @@ -196,7 +196,7 @@ impl Duration { /// Returns the total number of whole microseconds in the duration, /// or `None` on overflow (exceeding 2^63 microseconds in either direction). - pub fn num_microseconds(&self) -> Option { + 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; secs_part.checked_add(nanos_part as i64) @@ -204,7 +204,7 @@ impl Duration { /// Returns the total number of whole nanoseconds in the duration, /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). - pub fn num_nanoseconds(&self) -> Option { + 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(); secs_part.checked_add(nanos_part as i64) @@ -248,7 +248,7 @@ impl Duration { /// Returns the duration as an absolute (non-negative) value. #[inline] - pub fn abs(&self) -> Duration { + pub const fn abs(&self) -> Duration { if self.secs < 0 && self.nanos != 0 { Duration { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } } else { @@ -258,25 +258,25 @@ impl Duration { /// The minimum possible `Duration`: `i64::MIN` milliseconds. #[inline] - pub fn min_value() -> Duration { + pub const fn min_value() -> Duration { MIN } /// The maximum possible `Duration`: `i64::MAX` milliseconds. #[inline] - pub fn max_value() -> Duration { + pub const fn max_value() -> Duration { MAX } /// A duration where the stored seconds and nanoseconds are equal to zero. #[inline] - pub fn zero() -> Duration { + pub const fn zero() -> Duration { Duration { secs: 0, nanos: 0 } } /// Returns `true` if the duration equals `Duration::zero()`. #[inline] - pub fn is_zero(&self) -> bool { + pub const fn is_zero(&self) -> bool { self.secs == 0 && self.nanos == 0 } @@ -457,12 +457,12 @@ impl Error for OutOfRangeError { // Copied from libnum #[inline] -fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { +const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { (div_floor_64(this, other), mod_floor_64(this, other)) } #[inline] -fn div_floor_64(this: i64, other: i64) -> i64 { +const fn div_floor_64(this: i64, other: i64) -> i64 { match div_rem_64(this, other) { (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, (d, _) => d, @@ -470,7 +470,7 @@ fn div_floor_64(this: i64, other: i64) -> i64 { } #[inline] -fn mod_floor_64(this: i64, other: i64) -> i64 { +const fn mod_floor_64(this: i64, other: i64) -> i64 { match this % other { r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, r => r, @@ -478,7 +478,7 @@ fn mod_floor_64(this: i64, other: i64) -> i64 { } #[inline] -fn div_rem_64(this: i64, other: i64) -> (i64, i64) { +const fn div_rem_64(this: i64, other: i64) -> (i64, i64) { (this / other, this % other) } diff --git a/src/weekday.rs b/src/weekday.rs index c12dd01c64..72e384673f 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -72,7 +72,7 @@ impl Weekday { /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 #[inline] - pub fn number_from_monday(&self) -> u32 { + pub const fn number_from_monday(&self) -> u32 { self.num_days_from(Weekday::Mon) + 1 } @@ -82,7 +82,7 @@ impl Weekday { /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 #[inline] - pub fn number_from_sunday(&self) -> u32 { + pub const fn number_from_sunday(&self) -> u32 { self.num_days_from(Weekday::Sun) + 1 } @@ -92,7 +92,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] - pub fn num_days_from_monday(&self) -> u32 { + pub const fn num_days_from_monday(&self) -> u32 { self.num_days_from(Weekday::Mon) } @@ -102,7 +102,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 #[inline] - pub fn num_days_from_sunday(&self) -> u32 { + pub const fn num_days_from_sunday(&self) -> u32 { self.num_days_from(Weekday::Sun) } @@ -112,7 +112,7 @@ impl Weekday { /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 #[inline] - pub(crate) fn num_days_from(&self, day: Weekday) -> u32 { + pub(crate) const fn num_days_from(&self, day: Weekday) -> u32 { (*self as u32 + 7 - day as u32) % 7 } } From 64c5d7793a1a3e6ccbb2acc606b85f95830b7f83 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:53:20 +0100 Subject: [PATCH 14/18] Bump version to 0.4.24 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 14ae4d0322..e69847a44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chrono" -version = "0.4.23" +version = "0.4.24" description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" From b1e0963efc9544dee9e5b708e3abfea3e7eaa3d9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:53:58 +0100 Subject: [PATCH 15/18] Bump rust-cache action to v2 --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 41145a30a9..61bf164c8b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: | cargo fmt --check -- --color=always cargo fmt --check --manifest-path fuzz/Cargo.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 851ef5cec7..ffeed66e1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo test --all-features --color=always -- --color=always timezones_other: @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always @@ -45,7 +45,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: 1.38.0 - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 # run --lib and --doc to avoid the long running integration tests which are run elsewhere - run: cargo test --lib --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always - run: cargo test --doc --features unstable-locales,wasmbind,oldtime,clock,rustc-serialize,serde,winapi --color=always -- --color=always @@ -61,7 +61,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_version }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - 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 @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales no_std: @@ -90,7 +90,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo build --target ${{ matrix.target }} --color=always working-directory: ./ci/core-test @@ -105,7 +105,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - uses: actions/setup-node@v1 with: node-version: "12" @@ -114,7 +114,7 @@ jobs: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh wasm-pack --version - run: cargo build --target ${{ matrix.target }} --color=always - + features_check_wasm: strategy: matrix: @@ -126,7 +126,7 @@ jobs: with: targets: wasm32-unknown-unknown - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales cross-targets: @@ -138,7 +138,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: cargo install cross - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cross check --target ${{ matrix.target }} check-docs: From daa86a77d36d74f474913fd3b560a40f1424bd77 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 9 Mar 2023 10:55:02 +0100 Subject: [PATCH 16/18] Check benchmarks in CI --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffeed66e1c..89754956a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,7 @@ jobs: with: toolchain: ${{ matrix.rust_version }} - uses: Swatinem/rust-cache@v2 + - run: cargo check --benches - 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 From 9d376499b1deee94430937a96c78f8f997c32642 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 16 Mar 2023 10:59:23 +0100 Subject: [PATCH 17/18] Apply suggestions for clippy 1.68 --- src/format/scan.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 263fec556d..68e294c834 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -48,7 +48,7 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64) let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if !(b'0'..=b'9').contains(&c) { + if !c.is_ascii_digit() { if i < min { return Err(INVALID); } else { @@ -79,7 +79,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| ('0'..='9').contains(&c)); + let s = s.trim_left_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } From dc9ea3ab455bf705df7711a0bbdd73e4b7eadc12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B4she=20van=20der=20Sterre?= Date: Wed, 15 Mar 2023 16:46:46 +0100 Subject: [PATCH 18/18] fix IsoWeek so that its flags are always correct This fixes #295. PartialEq and PartialOrd are implemented by directly comparing the internal integer field, which includes the flags value. --- src/naive/isoweek.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 6a4fcfd110..45c2a81ede 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -46,7 +46,8 @@ pub(super) fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { (year, rawweek) } }; - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) } + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(flags.0) } } impl IsoWeek { @@ -164,4 +165,38 @@ mod tests { assert_eq!(maxweek.week0(), 0); assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } + + #[test] + fn test_iso_week_equivalence_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_equivalence_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } }