diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7e26d4e6..6209a0ea 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,9 +17,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo fmt --check -- --color=always - run: cargo fmt --check --manifest-path fuzz/Cargo.toml - - run: cargo clippy --color=always -- -D warnings - run: | - cargo clippy --color=always --target x86_64-pc-windows-msvc \ + cargo clippy --all-features --all-targets --color=always \ -- -D warnings - run: | cargo clippy --manifest-path fuzz/Cargo.toml --color=always \ @@ -27,6 +26,14 @@ jobs: env: RUSTFLAGS: "-Dwarnings" + toml: + runs-on: ubuntu-latest + container: + image: tamasfe/taplo:0.8.0 + steps: + - run: taplo lint + - run: taplo fmt --check --diff + cargo-deny: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef9709e2..0637aef6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,8 +28,7 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - 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 + - run: cargo test --all-features --color=always -- --color=always # later this may be able to be included with the below # kept separate for now as the following don't compile on 1.56.1 @@ -118,7 +117,6 @@ jobs: os: [ubuntu-latest] target: [ - wasm32-unknown-unknown, wasm32-wasi, wasm32-unknown-emscripten, aarch64-apple-ios, @@ -131,19 +129,11 @@ jobs: with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - uses: actions/setup-node@v3 with: node-version: "12" - - run: | - set -euxo pipefail - export RUST_BACKTRACE=1 - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf \ - | bash --noprofile --norc - wasm-pack --version - shell: bash - run: cargo build --target ${{ matrix.target }} --color=always - features_check_wasm: + test_wasm: strategy: matrix: os: [ubuntu-latest] @@ -155,10 +145,11 @@ jobs: targets: wasm32-unknown-unknown - uses: taiki-e/install-action@cargo-hack - 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 + - uses: actions/setup-node@v3 + - uses: jetli/wasm-pack-action@v0.4.0 + # The `TZ` and `NOW` variables are used to compare the results inside the WASM environment + # with the host system. + - run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind cross-targets: strategy: diff --git a/Cargo.toml b/Cargo.toml index 28a702f0..97e0de79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,12 @@ __doctest = [] 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} +rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } -js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API +js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_System_Time", "Win32_System_SystemInformation", "Win32_Foundation"], optional = true } diff --git a/Makefile b/Makefile deleted file mode 100644 index 598d446b..00000000 --- a/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# this Makefile is mostly for the packaging convenience. -# casual users should use `cargo` to retrieve the appropriate version of Chrono. - -CHANNEL=stable - -.PHONY: all -all: - @echo 'Try `cargo build` instead.' - -.PHONY: authors -authors: - echo 'Chrono is mainly written by Kang Seonghoon ,' > AUTHORS.txt - echo 'and also the following people (in ascending order):' >> AUTHORS.txt - echo >> AUTHORS.txt - git log --format='%aN <%aE>' | grep -v 'Kang Seonghoon' | sort -u >> AUTHORS.txt - -.PHONY: readme README.md -readme: README.md - -.PHONY: test -test: - CHANNEL=$(CHANNEL) ./ci/travis.sh - -.PHONY: doc -doc: authors readme - cargo doc --features 'serde bincode' diff --git a/ci/core-test/Cargo.toml b/ci/core-test/Cargo.toml index b35ff9a7..4258f9d9 100644 --- a/ci/core-test/Cargo.toml +++ b/ci/core-test/Cargo.toml @@ -2,8 +2,8 @@ name = "core-test" version = "0.1.0" authors = [ - "Kang Seonghoon ", - "Brandon W Maister ", + "Kang Seonghoon ", + "Brandon W Maister ", ] edition = "2018" @@ -11,4 +11,4 @@ edition = "2018" chrono = { path = "../..", default-features = false, features = ["serde"] } [features] -alloc = ["chrono/alloc"] \ No newline at end of file +alloc = ["chrono/alloc"] diff --git a/deny.toml b/deny.toml index 9b1539a0..7688d88d 100644 --- a/deny.toml +++ b/deny.toml @@ -4,8 +4,8 @@ copyleft = "deny" [advisories] ignore = [ - "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) - "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility + "RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion) + "RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility ] unmaintained = "deny" unsound = "deny" diff --git a/src/date.rs b/src/date.rs index a394006c..22af92e3 100644 --- a/src/date.rs +++ b/src/date.rs @@ -74,8 +74,6 @@ pub const MAX_DATE: Date = Date::::MAX_UTC; impl Date { /// Makes a new `Date` with given *UTC* date and offset. /// The local date should be constructed via the `TimeZone` trait. - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] #[must_use] pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date { @@ -85,7 +83,7 @@ impl Date { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// - /// Panics on invalid datetime. + /// Returns `None` on invalid datetime. #[inline] #[must_use] pub fn and_time(&self, time: NaiveTime) -> Option> { diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b910e95e..691fd5b2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -8,7 +8,6 @@ extern crate alloc; #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::Write; @@ -102,8 +101,6 @@ impl DateTime { /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc); /// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); /// ``` - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] #[must_use] pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { @@ -113,6 +110,13 @@ impl DateTime { /// Makes a new `DateTime` with given **local** datetime and offset that /// presents local timezone. /// + /// # Panics + /// + /// Panics if the local datetime can't be converted to UTC because it would be out of range. + /// + /// This can happen if `datetime` is near the end of the representable range of `NaiveDateTime`, + /// and the offset from UTC pushes it beyond that. + /// /// # Example /// /// ``` @@ -142,11 +146,19 @@ impl DateTime { DateTime { datetime: datetime_utc, offset } } - /// Retrieves a date component + /// Retrieves the date component with an associated timezone. /// /// Unless you are immediately planning on turning this into a `DateTime` - /// with the same Timezone you should use the - /// [`date_naive`](DateTime::date_naive) method. + /// with the same timezone you should use the [`date_naive`](DateTime::date_naive) method. + /// + /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, + /// so should be preferred to [`Date`] any time you truly want to operate on dates. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`Date`]. #[inline] #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] #[allow(deprecated)] @@ -155,10 +167,15 @@ impl DateTime { Date::from_utc(self.naive_local().date(), self.offset.clone()) } - /// Retrieves the Date without an associated timezone + /// Retrieves the date component. /// - /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, - /// so should be preferred to [`Date`] any time you truly want to operate on Dates. + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`NaiveDate`]. + /// + /// # Example /// /// ``` /// use chrono::prelude::*; @@ -174,8 +191,7 @@ impl DateTime { NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap() } - /// Retrieves a time component. - /// Unlike `date`, this is not associated to the time zone. + /// Retrieves the time component. #[inline] #[must_use] pub fn time(&self) -> NaiveTime { @@ -190,12 +206,7 @@ impl DateTime { self.datetime.timestamp() } - /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584 Million. (If this is a problem, please file - /// an issue to let me know what domain needs millisecond precision over - /// billions of years, I'm curious.) + /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC. /// /// # Example /// @@ -214,12 +225,7 @@ impl DateTime { self.datetime.timestamp_millis() } - /// Returns the number of non-leap-microseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584 Thousand. (If this is a problem, please file - /// an issue to let me know what domain needs microsecond precision over - /// millennia, I'm curious.) + /// Returns the number of non-leap-microseconds since January 1, 1970 UTC. /// /// # Example /// @@ -238,12 +244,15 @@ impl DateTime { self.datetime.timestamp_micros() } - /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584. (If this is a problem, please file - /// an issue to let me know what domain needs nanosecond precision over - /// millennia, I'm curious.) + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. /// /// # Example /// @@ -262,22 +271,18 @@ impl DateTime { self.datetime.timestamp_nanos() } - /// Returns the number of milliseconds since the last second boundary + /// Returns the number of milliseconds since the last second boundary. /// - /// warning: in event of a leap second, this may exceed 999 - /// - /// note: this is not the number of milliseconds since January 1, 1970 0:00:00 UTC + /// In event of a leap second this may exceed 999. #[inline] #[must_use] pub fn timestamp_subsec_millis(&self) -> u32 { self.datetime.timestamp_subsec_millis() } - /// Returns the number of microseconds since the last second boundary + /// Returns the number of microseconds since the last second boundary. /// - /// warning: in event of a leap second, this may exceed 999_999 - /// - /// note: this is not the number of microseconds since January 1, 1970 0:00:00 UTC + /// In event of a leap second this may exceed 999,999. #[inline] #[must_use] pub fn timestamp_subsec_micros(&self) -> u32 { @@ -286,9 +291,7 @@ impl DateTime { /// Returns the number of nanoseconds since the last second boundary /// - /// warning: in event of a leap second, this may exceed 999_999_999 - /// - /// note: this is not the number of nanoseconds since January 1, 1970 0:00:00 UTC + /// In event of a leap second this may exceed 999,999,999. #[inline] #[must_use] pub fn timestamp_subsec_nanos(&self) -> u32 { @@ -310,7 +313,8 @@ impl DateTime { } /// Changes the associated time zone. - /// The returned `DateTime` references the same instant of time from the perspective of the provided time zone. + /// The returned `DateTime` references the same instant of time from the perspective of the + /// provided time zone. #[inline] #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> DateTime { @@ -327,7 +331,9 @@ impl DateTime { /// Adds given `Duration` to the current date and time. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. #[inline] #[must_use] pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { @@ -338,10 +344,16 @@ impl DateTime { /// Adds given `Months` to the current date and time. /// - /// Returns `None` when it will result in overflow, or if the - /// local time is not valid on the newly calculated date. + /// Uses the last day of the month if the day does not exist in the resulting month. /// - /// See [`NaiveDate::checked_add_months`] for more details on behavior + /// See [`NaiveDate::checked_add_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date would be out of range. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[must_use] pub fn checked_add_months(self, rhs: Months) -> Option> { self.naive_local() @@ -352,7 +364,9 @@ impl DateTime { /// Subtracts given `Duration` from the current date and time. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. #[inline] #[must_use] pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { @@ -363,10 +377,16 @@ impl DateTime { /// Subtracts given `Months` from the current date and time. /// - /// Returns `None` when it will result in overflow, or if the - /// local time is not valid on the newly calculated date. + /// Uses the last day of the month if the day does not exist in the resulting month. /// - /// See [`NaiveDate::checked_sub_months`] for more details on behavior + /// See [`NaiveDate::checked_sub_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date would be out of range. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[must_use] pub fn checked_sub_months(self, rhs: Months) -> Option> { self.naive_local() @@ -375,9 +395,14 @@ impl DateTime { .single() } - /// Add a duration in [`Days`] to the date part of the `DateTime` + /// Add a duration in [`Days`] to the date part of the `DateTime`. /// - /// Returns `None` if the resulting date would be out of range. + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date would be out of range. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[must_use] pub fn checked_add_days(self, days: Days) -> Option { self.naive_local() @@ -386,9 +411,14 @@ impl DateTime { .single() } - /// Subtract a duration in [`Days`] from the date part of the `DateTime` + /// Subtract a duration in [`Days`] from the date part of the `DateTime`. /// - /// Returns `None` if the resulting date would be out of range. + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date would be out of range. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[must_use] pub fn checked_sub_days(self, days: Days) -> Option { self.naive_local() @@ -401,8 +431,11 @@ impl DateTime { /// This does not overflow or underflow at all. #[inline] #[must_use] - pub fn signed_duration_since(self, rhs: DateTime) -> TimeDelta { - self.datetime.signed_duration_since(rhs.datetime) + pub fn signed_duration_since( + self, + rhs: impl Borrow>, + ) -> TimeDelta { + self.datetime.signed_duration_since(rhs.borrow().datetime) } /// Returns a view to the naive UTC datetime. @@ -413,6 +446,12 @@ impl DateTime { } /// Returns a view to the naive local datetime. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local datetime outside of the + /// representable range of a [`NaiveDateTime`]. #[inline] #[must_use] pub fn naive_local(&self) -> NaiveDateTime { @@ -420,6 +459,10 @@ impl DateTime { } /// Retrieve the elapsed years from now to the given [`DateTime`]. + /// + /// # Errors + /// + /// Returns `None` if `base < self`. #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); @@ -715,6 +758,11 @@ where Tz::Offset: fmt::Display, { /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + /// + /// # Panics + /// + /// Panics if the date can not be represented in this format: the year may not be negative and + /// can not have more than 4 digits. #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] @@ -917,35 +965,112 @@ impl Datelike for DateTime { } #[inline] + /// Makes a new `DateTime` with the year number changed, while keeping the same month and day. + /// + /// See also the [`NaiveDate::with_year`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - When the `NaiveDateTime` would be out of range. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. fn with_year(&self, year: i32) -> Option> { map_local(self, |datetime| datetime.with_year(year)) } + /// Makes a new `DateTime` with the month number (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_month`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `month` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_month(&self, month: u32) -> Option> { map_local(self, |datetime| datetime.with_month(month)) } + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `month0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_month0(&self, month0: u32) -> Option> { map_local(self, |datetime| datetime.with_month0(month0)) } + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `day` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_day(&self, day: u32) -> Option> { map_local(self, |datetime| datetime.with_day(day)) } + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `day0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_day0(&self, day0: u32) -> Option> { map_local(self, |datetime| datetime.with_day0(day0)) } + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `ordinal` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_ordinal(&self, ordinal: u32) -> Option> { map_local(self, |datetime| datetime.with_ordinal(ordinal)) } + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist. + /// - The value for `ordinal0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option> { map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) @@ -970,21 +1095,64 @@ impl Timelike for DateTime { self.naive_local().nanosecond() } + /// Makes a new `DateTime` with the hour number changed. + /// + /// See also the [`NaiveTime::with_hour`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `hour` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_hour(&self, hour: u32) -> Option> { map_local(self, |datetime| datetime.with_hour(hour)) } + /// Makes a new `DateTime` with the minute number changed. + /// + /// See also the [`NaiveTime::with_minute`] method. + /// + /// # Errors + /// + /// - The value for `minute` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_minute(&self, min: u32) -> Option> { map_local(self, |datetime| datetime.with_minute(min)) } + /// Makes a new `DateTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// See also the [`NaiveTime::with_second`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `second` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. #[inline] fn with_second(&self, sec: u32) -> Option> { map_local(self, |datetime| datetime.with_second(sec)) } + /// Makes a new `DateTime` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// As with the [`NaiveDateTime::nanosecond`] method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// See also the [`NaiveTime::with_nanosecond`] method. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. #[inline] fn with_nanosecond(&self, nano: u32) -> Option> { map_local(self, |datetime| datetime.with_nanosecond(nano)) @@ -1099,6 +1267,15 @@ impl Sub> for DateTime { } } +impl Sub<&DateTime> for DateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: &DateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + impl Add for DateTime { type Output = DateTime; @@ -1299,34 +1476,6 @@ where } } -#[test] -fn test_add_sub_months() { - let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); - assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap()); - - let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap(); - assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); - assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap()); - - let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); - assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap()); - - let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap(); - assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); - assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap()); -} - -#[test] -fn test_auto_conversion() { - let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); - let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) - .unwrap() - .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) - .unwrap(); - let utc_dt2: DateTime = cdt_dt.into(); - assert_eq!(utc_dt, utc_dt2); -} - #[cfg(all(test, feature = "serde"))] fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index f1fa415a..90c1b4ff 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -158,7 +158,6 @@ pub mod ts_nanoseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, @@ -186,7 +185,6 @@ pub mod ts_nanoseconds { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -285,7 +283,6 @@ pub mod ts_nanoseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, @@ -316,7 +313,6 @@ pub mod ts_nanoseconds_option { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -417,7 +413,6 @@ pub mod ts_microseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, @@ -445,7 +440,6 @@ pub mod ts_microseconds { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -543,7 +537,6 @@ pub mod ts_microseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, @@ -574,7 +567,6 @@ pub mod ts_microseconds_option { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -675,7 +667,6 @@ pub mod ts_milliseconds { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, @@ -703,7 +694,6 @@ pub mod ts_milliseconds { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -798,7 +788,6 @@ pub mod ts_milliseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, @@ -840,7 +829,6 @@ pub mod ts_milliseconds_option { /// assert_eq!(t, E::V(S { time: None })); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -942,7 +930,6 @@ pub mod ts_seconds { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, @@ -970,7 +957,6 @@ pub mod ts_seconds { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -1062,7 +1048,6 @@ pub mod ts_seconds_option { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option>, serializer: S) -> Result where S: ser::Serializer, @@ -1093,7 +1078,6 @@ pub mod ts_seconds_option { /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> where D: de::Deserializer<'de>, @@ -1136,30 +1120,36 @@ pub mod ts_seconds_option { } } -#[test] -fn test_serde_serialize() { - super::test_encodable_json(serde_json::to_string, serde_json::to_string); -} +#[cfg(test)] +mod tests { + use crate::datetime::{test_decodable_json, test_encodable_json}; + use crate::{DateTime, TimeZone, Utc}; -#[cfg(feature = "clock")] -#[test] -fn test_serde_deserialize() { - super::test_decodable_json( - |input| serde_json::from_str(input), - |input| serde_json::from_str(input), - |input| serde_json::from_str(input), - ); -} + #[test] + fn test_serde_serialize() { + test_encodable_json(serde_json::to_string, serde_json::to_string); + } -#[test] -fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use bincode::{deserialize, serialize}; + #[cfg(feature = "clock")] + #[test] + fn test_serde_deserialize() { + test_decodable_json( + |input| serde_json::from_str(input), + |input| serde_json::from_str(input), + |input| serde_json::from_str(input), + ); + } - let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); - let encoded = serialize(&dt).unwrap(); - let decoded: DateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - assert_eq!(dt.offset(), decoded.offset()); + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: DateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset(), decoded.offset()); + } } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 55fa8f37..4517bb9b 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -240,6 +240,7 @@ fn test_datetime_sub_months() { } // local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] fn ymdhms( fixedoffset: &FixedOffset, year: i32, @@ -253,6 +254,7 @@ fn ymdhms( } // local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] fn ymdhms_milli( fixedoffset: &FixedOffset, year: i32, @@ -271,6 +273,7 @@ fn ymdhms_milli( } // local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] fn ymdhms_micro( fixedoffset: &FixedOffset, year: i32, @@ -289,6 +292,7 @@ fn ymdhms_micro( } // local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] fn ymdhms_nano( fixedoffset: &FixedOffset, year: i32, @@ -400,6 +404,20 @@ fn test_datetime_offset() { assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); } +#[test] +#[allow(clippy::needless_borrow, clippy::op_ref)] +fn signed_duration_since_autoref() { + let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap(); + let diff1 = dt1.signed_duration_since(dt2); // Copy/consume + let diff2 = dt2.signed_duration_since(&dt1); // Take by reference + assert_eq!(diff1, -diff2); + + let diff1 = dt1 - &dt2; // We can choose to substract rhs by reference + let diff2 = dt2 - dt1; // Or consume rhs + assert_eq!(diff1, -diff2); +} + #[test] fn test_datetime_date_and_time() { let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); @@ -901,6 +919,10 @@ fn test_utc_datetime_from_str() { Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap()) ); + assert_eq!( + DateTime::::parse_from_str("0", "%s").unwrap(), + NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() + ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), @@ -1549,3 +1571,31 @@ fn test_datetime_fixed_offset() { let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap(); assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); } + +#[test] +fn test_add_sub_months() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap()); +} + +#[test] +fn test_auto_conversion() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) + .unwrap() + .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) + .unwrap(); + let utc_dt2: DateTime = cdt_dt.into(); + assert_eq!(utc_dt, utc_dt2); +} diff --git a/src/format/mod.rs b/src/format/mod.rs index eb21e8aa..d7f46c67 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -989,12 +989,12 @@ impl FromStr for Weekday { /// Formats single formatting item #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] -pub fn format_item_localized<'a>( +pub fn format_item_localized( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - item: &Item<'a>, + item: &Item<'_>, locale: Locale, ) -> fmt::Result { let mut result = String::new(); diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 7f076d5b..ed6c3670 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -620,7 +620,12 @@ impl Parsed { /// plus a time zone offset. /// Either way those fields have to be consistent to each other. pub fn to_datetime(&self) -> ParseResult> { - let offset = self.offset.ok_or(NOT_ENOUGH)?; + // If there is no explicit offset, consider a timestamp value as indication of a UTC value. + let offset = match (self.offset, self.timestamp) { + (Some(off), _) => off, + (None, Some(_)) => 0, // UNIX timestamp may assume 0 offset + (None, None) => return Err(NOT_ENOUGH), + }; let datetime = self.to_naive_datetime_with_offset(offset)?; let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; diff --git a/src/format/scan.rs b/src/format/scan.rs index 50315fef..29fa70d4 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -414,98 +414,103 @@ enum CommentState { } #[cfg(test)] -#[test] -fn test_rfc2822_comments() { - let testdata = [ - ("", Err(TOO_SHORT)), - (" ", Err(TOO_SHORT)), - ("x", Err(INVALID)), - ("(", Err(TOO_SHORT)), - ("()", Ok("")), - (" \r\n\t()", Ok("")), - ("() ", Ok(" ")), - ("()z", Ok("z")), - ("(x)", Ok("")), - ("(())", Ok("")), - ("((()))", Ok("")), - ("(x(x(x)x)x)", Ok("")), - ("( x ( x ( x ) x ) x )", Ok("")), - (r"(\)", Err(TOO_SHORT)), - (r"(\()", Ok("")), - (r"(\))", Ok("")), - (r"(\\)", Ok("")), - ("(()())", Ok("")), - ("( x ( x ) x ( x ) x )", Ok("")), - ]; +mod tests { + use super::{comment_2822, consume_colon_maybe, s_next, space, trim1}; + use crate::format::{INVALID, TOO_SHORT}; - for (test_in, expected) in testdata.iter() { - let actual = comment_2822(test_in).map(|(s, _)| s); - assert_eq!( - *expected, actual, - "{:?} expected to produce {:?}, but produced {:?}.", - test_in, expected, actual - ); + #[test] + fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + (" ", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + (" \r\n\t()", Ok("")), + ("() ", Ok(" ")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata.iter() { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!( + *expected, actual, + "{:?} expected to produce {:?}, but produced {:?}.", + test_in, expected, actual + ); + } + } + + #[test] + fn test_space() { + assert_eq!(space(""), Err(TOO_SHORT)); + assert_eq!(space(" "), Ok("")); + assert_eq!(space(" \t"), Ok("")); + assert_eq!(space(" \ta"), Ok("a")); + assert_eq!(space(" \ta "), Ok("a ")); + assert_eq!(space("a"), Err(INVALID)); + assert_eq!(space("a "), Err(INVALID)); + } + + #[test] + fn test_s_next() { + assert_eq!(s_next(""), ""); + assert_eq!(s_next(" "), ""); + assert_eq!(s_next("a"), ""); + assert_eq!(s_next("ab"), "b"); + assert_eq!(s_next("abc"), "bc"); + assert_eq!(s_next("😾b"), "b"); + assert_eq!(s_next("a😾"), "😾"); + assert_eq!(s_next("😾bc"), "bc"); + assert_eq!(s_next("a😾c"), "😾c"); + } + + #[test] + fn test_trim1() { + assert_eq!(trim1(""), ""); + assert_eq!(trim1(" "), ""); + assert_eq!(trim1("\t"), ""); + assert_eq!(trim1("\t\t"), "\t"); + assert_eq!(trim1(" "), " "); + assert_eq!(trim1("a"), "a"); + assert_eq!(trim1("a "), "a "); + assert_eq!(trim1("ab"), "ab"); + assert_eq!(trim1("😼"), "😼"); + assert_eq!(trim1("😼b"), "😼b"); + } + + #[test] + fn test_consume_colon_maybe() { + assert_eq!(consume_colon_maybe(""), Ok("")); + assert_eq!(consume_colon_maybe(" "), Ok(" ")); + assert_eq!(consume_colon_maybe("\n"), Ok("\n")); + assert_eq!(consume_colon_maybe(" "), Ok(" ")); + assert_eq!(consume_colon_maybe(":"), Ok("")); + assert_eq!(consume_colon_maybe(" :"), Ok(" :")); + assert_eq!(consume_colon_maybe(": "), Ok(" ")); + assert_eq!(consume_colon_maybe(" : "), Ok(" : ")); + assert_eq!(consume_colon_maybe(": "), Ok(" ")); + assert_eq!(consume_colon_maybe(" :"), Ok(" :")); + assert_eq!(consume_colon_maybe(":: "), Ok(": ")); + assert_eq!(consume_colon_maybe("😸"), Ok("😸")); + assert_eq!(consume_colon_maybe("😸😸"), Ok("😸😸")); + assert_eq!(consume_colon_maybe("😸:"), Ok("😸:")); + assert_eq!(consume_colon_maybe("😸 "), Ok("😸 ")); + assert_eq!(consume_colon_maybe(":😸"), Ok("😸")); + assert_eq!(consume_colon_maybe(":😸 "), Ok("😸 ")); + assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸")); + assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸")); + assert_eq!(consume_colon_maybe(": :😸"), Ok(" :😸")); } } - -#[test] -fn test_space() { - assert_eq!(space(""), Err(TOO_SHORT)); - assert_eq!(space(" "), Ok("")); - assert_eq!(space(" \t"), Ok("")); - assert_eq!(space(" \ta"), Ok("a")); - assert_eq!(space(" \ta "), Ok("a ")); - assert_eq!(space("a"), Err(INVALID)); - assert_eq!(space("a "), Err(INVALID)); -} - -#[test] -fn test_s_next() { - assert_eq!(s_next(""), ""); - assert_eq!(s_next(" "), ""); - assert_eq!(s_next("a"), ""); - assert_eq!(s_next("ab"), "b"); - assert_eq!(s_next("abc"), "bc"); - assert_eq!(s_next("😾b"), "b"); - assert_eq!(s_next("a😾"), "😾"); - assert_eq!(s_next("😾bc"), "bc"); - assert_eq!(s_next("a😾c"), "😾c"); -} - -#[test] -fn test_trim1() { - assert_eq!(trim1(""), ""); - assert_eq!(trim1(" "), ""); - assert_eq!(trim1("\t"), ""); - assert_eq!(trim1("\t\t"), "\t"); - assert_eq!(trim1(" "), " "); - assert_eq!(trim1("a"), "a"); - assert_eq!(trim1("a "), "a "); - assert_eq!(trim1("ab"), "ab"); - assert_eq!(trim1("😼"), "😼"); - assert_eq!(trim1("😼b"), "😼b"); -} - -#[test] -fn test_consume_colon_maybe() { - assert_eq!(consume_colon_maybe(""), Ok("")); - assert_eq!(consume_colon_maybe(" "), Ok(" ")); - assert_eq!(consume_colon_maybe("\n"), Ok("\n")); - assert_eq!(consume_colon_maybe(" "), Ok(" ")); - assert_eq!(consume_colon_maybe(":"), Ok("")); - assert_eq!(consume_colon_maybe(" :"), Ok(" :")); - assert_eq!(consume_colon_maybe(": "), Ok(" ")); - assert_eq!(consume_colon_maybe(" : "), Ok(" : ")); - assert_eq!(consume_colon_maybe(": "), Ok(" ")); - assert_eq!(consume_colon_maybe(" :"), Ok(" :")); - assert_eq!(consume_colon_maybe(":: "), Ok(": ")); - assert_eq!(consume_colon_maybe("😸"), Ok("😸")); - assert_eq!(consume_colon_maybe("😸😸"), Ok("😸😸")); - assert_eq!(consume_colon_maybe("😸:"), Ok("😸:")); - assert_eq!(consume_colon_maybe("😸 "), Ok("😸 ")); - assert_eq!(consume_colon_maybe(":😸"), Ok("😸")); - assert_eq!(consume_colon_maybe(":😸 "), Ok("😸 ")); - assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸")); - assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸")); - assert_eq!(consume_colon_maybe(": :😸"), Ok(" :😸")); -} diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 6d205653..d08d4e43 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -509,293 +509,301 @@ impl<'a> Iterator for StrftimeItems<'a> { } #[cfg(test)] -#[test] -fn test_strftime_items() { - fn parse_and_collect(s: &str) -> Vec> { - // map any error into `[Item::Error]`. useful for easy testing. - eprintln!("test_strftime_items: parse_and_collect({:?})", s); - let items = StrftimeItems::new(s); - let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); - items.collect::>>().unwrap_or_else(|| vec![Item::Error]) +mod tests { + #[cfg(feature = "unstable-locales")] + use super::Locale; + use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, StrftimeItems}; + use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; + + #[test] + fn test_strftime_items() { + fn parse_and_collect(s: &str) -> Vec> { + // map any error into `[Item::Error]`. useful for easy testing. + eprintln!("test_strftime_items: parse_and_collect({:?})", s); + let items = StrftimeItems::new(s); + let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); + items.collect::>>().unwrap_or_else(|| vec![Item::Error]) + } + + assert_eq!(parse_and_collect(""), []); + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [sp!(" ")]); + assert_eq!(parse_and_collect("a"), [lit!("a")]); + assert_eq!(parse_and_collect("ab"), [lit!("ab")]); + assert_eq!(parse_and_collect("😽"), [lit!("😽")]); + assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]); + assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]); + assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); + assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); + assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); + assert_eq!( + parse_and_collect("a b\t\nc"), + [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] + ); + assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); + assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); + assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); + assert_eq!( + parse_and_collect("%Y-%m-%d"), + [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] + ); + assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); + assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]); + assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]); + assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 πŸ˜½γ―γ„πŸ˜½ ハンバーガー"), + [sp!(" "), lit!("😽"), sp!(" "), lit!("πŸ˜½γ―γ„πŸ˜½"), sp!(" "), lit!("ハンバーガー")] + ); + assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]); + assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]); + assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]); + assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); + assert_eq!(parse_and_collect("%"), [Item::Error]); + assert_eq!(parse_and_collect("%%"), [lit!("%")]); + assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]); + assert_eq!(parse_and_collect("%%a%"), [Item::Error]); + assert_eq!(parse_and_collect("%😽"), [Item::Error]); + assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); + assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); + assert_eq!( + parse_and_collect("%%%%ハンバーガー"), + [lit!("%"), lit!("%"), lit!("ハンバーガー")] + ); + assert_eq!(parse_and_collect("foo%?"), [Item::Error]); + assert_eq!(parse_and_collect("bar%42"), [Item::Error]); + assert_eq!(parse_and_collect("quux% +"), [Item::Error]); + assert_eq!(parse_and_collect("%.Z"), [Item::Error]); + assert_eq!(parse_and_collect("%:Z"), [Item::Error]); + assert_eq!(parse_and_collect("%-Z"), [Item::Error]); + assert_eq!(parse_and_collect("%0Z"), [Item::Error]); + assert_eq!(parse_and_collect("%_Z"), [Item::Error]); + assert_eq!(parse_and_collect("%.j"), [Item::Error]); + assert_eq!(parse_and_collect("%:j"), [Item::Error]); + assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); + assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); + assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); + assert_eq!(parse_and_collect("%.e"), [Item::Error]); + assert_eq!(parse_and_collect("%:e"), [Item::Error]); + assert_eq!(parse_and_collect("%-e"), [num!(Day)]); + assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); + assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); + assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]); + assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); + assert_eq!(parse_and_collect("%#m"), [Item::Error]); } - assert_eq!(parse_and_collect(""), []); - assert_eq!(parse_and_collect(" "), [sp!(" ")]); - assert_eq!(parse_and_collect(" "), [sp!(" ")]); - // ne! - assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]); - // eq! - assert_eq!(parse_and_collect(" "), [sp!(" ")]); - assert_eq!(parse_and_collect("a"), [lit!("a")]); - assert_eq!(parse_and_collect("ab"), [lit!("ab")]); - assert_eq!(parse_and_collect("😽"), [lit!("😽")]); - assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]); - assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]); - assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); - assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); - // ne! - assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]); - assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]); - assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]); - // eq! - assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); - assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); - assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); - assert_eq!( - parse_and_collect("a b\t\nc"), - [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] - ); - assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); - assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); - assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); - assert_eq!( - parse_and_collect("%Y-%m-%d"), - [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] - ); - assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]); - assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]); - assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]); - assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); - assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]); - assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]); - assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]); - assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]); - assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]); - assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]); - assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]); - assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]); - assert_eq!( - parse_and_collect(" 😽 😽 "), - [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] - ); - assert_eq!( - parse_and_collect(" 😽 😽 "), - [sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")] - ); - assert_eq!( - parse_and_collect(" 😽 😽😽 "), - [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] - ); - assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]); - assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); - assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); - assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); - assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]); - assert_eq!( - parse_and_collect(" 😽 😽😽 "), - [sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")] - ); - assert_eq!( - parse_and_collect(" 😽 πŸ˜½γ―γ„πŸ˜½ ハンバーガー"), - [sp!(" "), lit!("😽"), sp!(" "), lit!("πŸ˜½γ―γ„πŸ˜½"), sp!(" "), lit!("ハンバーガー")] - ); - assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]); - assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]); - assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); - assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]); - assert_eq!( - parse_and_collect("100%%😽%%a"), - [lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")] - ); - assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]); - assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); - assert_eq!(parse_and_collect("%"), [Item::Error]); - assert_eq!(parse_and_collect("%%"), [lit!("%")]); - assert_eq!(parse_and_collect("%%%"), [Item::Error]); - assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]); - assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]); - assert_eq!(parse_and_collect("%%a%"), [Item::Error]); - assert_eq!(parse_and_collect("%😽"), [Item::Error]); - assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); - assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); - assert_eq!(parse_and_collect("%%%%ハンバーガー"), [lit!("%"), lit!("%"), lit!("ハンバーガー")]); - assert_eq!(parse_and_collect("foo%?"), [Item::Error]); - assert_eq!(parse_and_collect("bar%42"), [Item::Error]); - assert_eq!(parse_and_collect("quux% +"), [Item::Error]); - assert_eq!(parse_and_collect("%.Z"), [Item::Error]); - assert_eq!(parse_and_collect("%:Z"), [Item::Error]); - assert_eq!(parse_and_collect("%-Z"), [Item::Error]); - assert_eq!(parse_and_collect("%0Z"), [Item::Error]); - assert_eq!(parse_and_collect("%_Z"), [Item::Error]); - assert_eq!(parse_and_collect("%.j"), [Item::Error]); - assert_eq!(parse_and_collect("%:j"), [Item::Error]); - assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); - assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); - assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); - assert_eq!(parse_and_collect("%.e"), [Item::Error]); - assert_eq!(parse_and_collect("%:e"), [Item::Error]); - assert_eq!(parse_and_collect("%-e"), [num!(Day)]); - assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); - assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); - assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); - assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]); - assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]); - assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]); - assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]); - assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); - assert_eq!(parse_and_collect("%#m"), [Item::Error]); -} + #[test] + fn test_strftime_docs() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); -#[cfg(test)] -#[test] -fn test_strftime_docs() { - use crate::NaiveDate; - use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; + // date specifiers + assert_eq!(dt.format("%Y").to_string(), "2001"); + assert_eq!(dt.format("%C").to_string(), "20"); + assert_eq!(dt.format("%y").to_string(), "01"); + assert_eq!(dt.format("%m").to_string(), "07"); + assert_eq!(dt.format("%b").to_string(), "Jul"); + assert_eq!(dt.format("%B").to_string(), "July"); + assert_eq!(dt.format("%h").to_string(), "Jul"); + assert_eq!(dt.format("%d").to_string(), "08"); + assert_eq!(dt.format("%e").to_string(), " 8"); + assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); + assert_eq!(dt.format("%a").to_string(), "Sun"); + 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(), "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"); + assert_eq!(dt.format("%V").to_string(), "27"); + assert_eq!(dt.format("%j").to_string(), "189"); + assert_eq!(dt.format("%D").to_string(), "07/08/01"); + assert_eq!(dt.format("%x").to_string(), "07/08/01"); + assert_eq!(dt.format("%F").to_string(), "2001-07-08"); + assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); - let dt = FixedOffset::east_opt(34200) - .unwrap() - .from_local_datetime( - &NaiveDate::from_ymd_opt(2001, 7, 8) + // time specifiers + assert_eq!(dt.format("%H").to_string(), "00"); + assert_eq!(dt.format("%k").to_string(), " 0"); + assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); + assert_eq!(dt.format("%I").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); + assert_eq!(dt.format("%P").to_string(), "am"); + assert_eq!(dt.format("%p").to_string(), "AM"); + assert_eq!(dt.format("%M").to_string(), "34"); + assert_eq!(dt.format("%S").to_string(), "60"); + assert_eq!(dt.format("%f").to_string(), "026490708"); + assert_eq!(dt.format("%.f").to_string(), ".026490708"); + assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); + assert_eq!(dt.format("%.3f").to_string(), ".026"); + assert_eq!(dt.format("%.6f").to_string(), ".026490"); + assert_eq!(dt.format("%.9f").to_string(), ".026490708"); + assert_eq!(dt.format("%3f").to_string(), "026"); + assert_eq!(dt.format("%6f").to_string(), "026490"); + assert_eq!(dt.format("%9f").to_string(), "026490708"); + assert_eq!(dt.format("%R").to_string(), "00:34"); + assert_eq!(dt.format("%T").to_string(), "00:34:60"); + assert_eq!(dt.format("%X").to_string(), "00:34:60"); + assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); + + // time zone specifiers + //assert_eq!(dt.format("%Z").to_string(), "ACST"); + assert_eq!(dt.format("%z").to_string(), "+0930"); + assert_eq!(dt.format("%:z").to_string(), "+09:30"); + assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); + assert_eq!(dt.format("%:::z").to_string(), "+09"); + + // date & time specifiers + assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); + assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); + + assert_eq!( + dt.with_timezone(&Utc).format("%+").to_string(), + "2001-07-07T15:04:60.026490708+00:00" + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+") .unwrap() - .and_hms_nano_opt(0, 34, 59, 1_026_490_708) - .unwrap(), - ) - .unwrap(); + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+") + .unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+") + .unwrap() + ); - // date specifiers - assert_eq!(dt.format("%Y").to_string(), "2001"); - assert_eq!(dt.format("%C").to_string(), "20"); - assert_eq!(dt.format("%y").to_string(), "01"); - assert_eq!(dt.format("%m").to_string(), "07"); - assert_eq!(dt.format("%b").to_string(), "Jul"); - assert_eq!(dt.format("%B").to_string(), "July"); - assert_eq!(dt.format("%h").to_string(), "Jul"); - assert_eq!(dt.format("%d").to_string(), "08"); - assert_eq!(dt.format("%e").to_string(), " 8"); - assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); - assert_eq!(dt.format("%a").to_string(), "Sun"); - 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(), "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"); - assert_eq!(dt.format("%V").to_string(), "27"); - assert_eq!(dt.format("%j").to_string(), "189"); - assert_eq!(dt.format("%D").to_string(), "07/08/01"); - assert_eq!(dt.format("%x").to_string(), "07/08/01"); - assert_eq!(dt.format("%F").to_string(), "2001-07-08"); - assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); + assert_eq!( + dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), + "2001-07-08T00:34:60.026490+09:30" + ); + assert_eq!(dt.format("%s").to_string(), "994518299"); - // time specifiers - assert_eq!(dt.format("%H").to_string(), "00"); - assert_eq!(dt.format("%k").to_string(), " 0"); - assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); - assert_eq!(dt.format("%I").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); - assert_eq!(dt.format("%P").to_string(), "am"); - assert_eq!(dt.format("%p").to_string(), "AM"); - assert_eq!(dt.format("%M").to_string(), "34"); - assert_eq!(dt.format("%S").to_string(), "60"); - assert_eq!(dt.format("%f").to_string(), "026490708"); - assert_eq!(dt.format("%.f").to_string(), ".026490708"); - assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); - assert_eq!(dt.format("%.3f").to_string(), ".026"); - assert_eq!(dt.format("%.6f").to_string(), ".026490"); - assert_eq!(dt.format("%.9f").to_string(), ".026490708"); - assert_eq!(dt.format("%3f").to_string(), "026"); - assert_eq!(dt.format("%6f").to_string(), "026490"); - assert_eq!(dt.format("%9f").to_string(), "026490708"); - assert_eq!(dt.format("%R").to_string(), "00:34"); - assert_eq!(dt.format("%T").to_string(), "00:34:60"); - assert_eq!(dt.format("%X").to_string(), "00:34:60"); - assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); + // special specifiers + assert_eq!(dt.format("%t").to_string(), "\t"); + assert_eq!(dt.format("%n").to_string(), "\n"); + assert_eq!(dt.format("%%").to_string(), "%"); - // time zone specifiers - //assert_eq!(dt.format("%Z").to_string(), "ACST"); - assert_eq!(dt.format("%z").to_string(), "+0930"); - assert_eq!(dt.format("%:z").to_string(), "+09:30"); - assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); - assert_eq!(dt.format("%:::z").to_string(), "+09"); + // complex format specifiers + assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); + assert_eq!( + dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), + " 20010807%%\t00:am:3460+09\t" + ); + } - // date & time specifiers - assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); - assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); + #[cfg(feature = "unstable-locales")] + #[test] + fn test_strftime_docs_localized() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); - assert_eq!( - dt.with_timezone(&Utc).format("%+").to_string(), - "2001-07-07T15:04:60.026490708+00:00" - ); - assert_eq!( - dt.with_timezone(&Utc), - DateTime::::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() - ); - assert_eq!( - dt.with_timezone(&Utc), - DateTime::::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() - ); - assert_eq!( - dt.with_timezone(&Utc), - DateTime::::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() - ); + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); + assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); + assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); + assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); + assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); - assert_eq!( - dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), - "2001-07-08T00:34:60.026490+09:30" - ); - assert_eq!(dt.format("%s").to_string(), "994518299"); + // time specifiers + assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); + assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); - // special specifiers - assert_eq!(dt.format("%t").to_string(), "\t"); - assert_eq!(dt.format("%n").to_string(), "\n"); - assert_eq!(dt.format("%%").to_string(), "%"); + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::fr_BE).to_string(), + "dim 08 jui 2001 00:34:60 +09:30" + ); - // complex format specifiers - assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); - assert_eq!( - dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), - " 20010807%%\t00:am:3460+09\t" - ); -} - -#[cfg(feature = "unstable-locales")] -#[test] -fn test_strftime_docs_localized() { - use crate::{FixedOffset, NaiveDate}; - - let dt = NaiveDate::from_ymd_opt(2001, 7, 8) - .and_then(|d| d.and_hms_nano_opt(0, 34, 59, 1_026_490_708)) - .unwrap() - .and_local_timezone(FixedOffset::east_opt(34200).unwrap()) - .unwrap(); - - // date specifiers - assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); - assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); - assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); - assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); - assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); - assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); - - // time specifiers - assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); - assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); - - // date & time specifiers - assert_eq!( - dt.format_localized("%c", Locale::fr_BE).to_string(), - "dim 08 jui 2001 00:34:60 +09:30" - ); - - let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); - - // date specifiers - assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); - assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); - assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); - assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); - assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); - assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); - assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); - assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); - assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); + let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); + + // date specifiers + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); + assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); + assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); + assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); + assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); + } } diff --git a/src/lib.rs b/src/lib.rs index 125a5f72..5429316d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -374,6 +374,7 @@ #![deny(missing_debug_implementations)] #![warn(unreachable_pub)] #![deny(dead_code)] +#![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/src/month.rs b/src/month.rs index 479e632c..822fbfa2 100644 --- a/src/month.rs +++ b/src/month.rs @@ -28,7 +28,7 @@ use crate::OutOfRange; /// Allows mapping from and to month, from 1-January to 12-December. /// Can be Serialized/Deserialized with serde // Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Month { @@ -180,7 +180,7 @@ impl TryFrom for Month { } /// A duration in calendar months -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Months(pub(crate) u32); @@ -245,8 +245,47 @@ mod month_serde { deserializer.deserialize_str(MonthVisitor) } } +} + +#[cfg(test)] +mod tests { + use super::Month; + use crate::{Datelike, OutOfRange, TimeZone, Utc}; #[test] + fn test_month_enum_try_from() { + assert_eq!(Month::try_from(1), Ok(Month::January)); + assert_eq!(Month::try_from(2), Ok(Month::February)); + assert_eq!(Month::try_from(12), Ok(Month::December)); + assert_eq!(Month::try_from(13), Err(OutOfRange::new())); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October)); + + let month = Month::January; + let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } + + #[test] + fn test_month_enum_succ_pred() { + assert_eq!(Month::January.succ(), Month::February); + assert_eq!(Month::December.succ(), Month::January); + assert_eq!(Month::January.pred(), Month::December); + assert_eq!(Month::February.pred(), Month::January); + } + + #[test] + fn test_month_partial_ord() { + assert!(Month::January <= Month::January); + assert!(Month::January < Month::February); + assert!(Month::January < Month::December); + assert!(Month::July >= Month::May); + assert!(Month::September > Month::March); + } + + #[test] + #[cfg(feature = "serde")] fn test_serde_serialize() { use serde_json::to_string; use Month::*; @@ -273,6 +312,7 @@ mod month_serde { } #[test] + #[cfg(feature = "serde")] fn test_serde_deserialize() { use serde_json::from_str; use Month::*; @@ -307,41 +347,3 @@ mod month_serde { } } } - -#[cfg(test)] -mod tests { - use super::Month; - use crate::{Datelike, OutOfRange, TimeZone, Utc}; - - #[test] - fn test_month_enum_try_from() { - assert_eq!(Month::try_from(1), Ok(Month::January)); - assert_eq!(Month::try_from(2), Ok(Month::February)); - assert_eq!(Month::try_from(12), Ok(Month::December)); - assert_eq!(Month::try_from(13), Err(OutOfRange::new())); - - let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); - assert_eq!(Month::try_from(date.month() as u8).ok(), Some(Month::October)); - - let month = Month::January; - let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); - assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); - } - - #[test] - fn test_month_enum_succ_pred() { - assert_eq!(Month::January.succ(), Month::February); - assert_eq!(Month::December.succ(), Month::January); - assert_eq!(Month::January.pred(), Month::December); - assert_eq!(Month::February.pred(), Month::January); - } - - #[test] - fn test_month_partial_ord() { - assert!(Month::January <= Month::January); - assert!(Month::January < Month::February); - assert!(Month::January < Month::December); - assert!(Month::July >= Month::May); - assert!(Month::September > Month::March); - } -} diff --git a/src/naive/date.rs b/src/naive/date.rs index b367936f..e98615b4 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -5,6 +5,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; +use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; @@ -31,29 +32,6 @@ use super::isoweek; const MAX_YEAR: i32 = internals::MAX_YEAR; const MIN_YEAR: i32 = internals::MIN_YEAR; -// MAX_YEAR-12-31 minus 0000-01-01 -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days -// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days -#[cfg(test)] // only used for testing -const MAX_DAYS_FROM_YEAR_0: i32 = - MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365; - -// MIN_YEAR-01-01 minus 0000-01-01 -// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01 -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01) -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days -// -// n is set to 1000 for convenience. -#[cfg(test)] // only used for testing -const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4 - - (MIN_YEAR + 400_000) / 100 - + (MIN_YEAR + 400_000) / 400 - - 146_097_000; - -#[cfg(test)] // only used for testing, but duplicated in naive::datetime -const MAX_BITS: usize = 44; - /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first /// day of the week. #[derive(Debug)] @@ -65,6 +43,11 @@ pub struct NaiveWeek { impl NaiveWeek { /// Returns a date representing the first day of the week. /// + /// # Panics + /// + /// Panics if the first day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// /// # Examples /// /// ``` @@ -88,6 +71,11 @@ impl NaiveWeek { /// Returns a date representing the last day of the week. /// + /// # Panics + /// + /// Panics if the last day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// /// # Examples /// /// ``` @@ -113,6 +101,11 @@ impl NaiveWeek { /// [first_day](./struct.NaiveWeek.html#method.first_day) and /// [last_day](./struct.NaiveWeek.html#method.last_day) functions. /// + /// # Panics + /// + /// Panics if the either the first or last day of the week happens to fall just out of range of + /// `NaiveDate` (more than ca. 262,000 years away from common era). + /// /// # Examples /// /// ``` @@ -136,7 +129,7 @@ impl NaiveWeek { /// that adding `TimeDelta::days(1)` doesn't increment the day value as expected due to it being a /// fixed number of seconds. This difference applies only when dealing with `DateTime` data types /// and in other cases `TimeDelta::days(n)` and `Days::new(n)` are equivalent. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Days(pub(crate) u64); impl Days { @@ -218,34 +211,6 @@ impl arbitrary::Arbitrary<'_> for NaiveDate { } } -// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, -// we use a separate run-time test. -#[test] -fn test_date_bounds() { - let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap(); - let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); - assert!( - NaiveDate::MIN == calculated_min, - "`NaiveDate::MIN` should have a year flag {:?}", - calculated_min.of().flags() - ); - assert!( - NaiveDate::MAX == calculated_max, - "`NaiveDate::MAX` should have a year flag {:?}", - calculated_max.of().flags() - ); - - // let's also check that the entire range do not exceed 2^44 seconds - // (sometimes used for bounding `TimeDelta` against overflow) - let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); - let maxsecs = maxsecs + 86401; // also take care of DateTime - assert!( - maxsecs < (1 << MAX_BITS), - "The entire `NaiveDate` range somehow exceeds 2^{} seconds", - MAX_BITS - ); -} - 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 @@ -284,7 +249,10 @@ impl NaiveDate { /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) /// (year, month and day). /// - /// Panics on the out-of-range date, invalid month and/or day. + /// # Panics + /// + /// Panics if the specified calendar day does not exist, on invalid values for `month` or `day`, + /// or if `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] #[must_use] pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { @@ -294,7 +262,12 @@ impl NaiveDate { /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) /// (year, month and day). /// - /// Returns `None` on the out-of-range date, invalid month and/or day. + /// # Errors + /// + /// Returns `None` if: + /// - The specified calendar day does not exist (for example 2023-04-31). + /// - The value for `month` or `day` is invalid. + /// - `year` is out of range for `NaiveDate`. /// /// # Example /// @@ -319,7 +292,10 @@ impl NaiveDate { /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) /// (year and day of the year). /// - /// Panics on the out-of-range date and/or invalid day of year. + /// # Panics + /// + /// Panics if the specified ordinal day does not exist, on invalid values for `ordinal`, or if + /// `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] #[must_use] pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate { @@ -329,7 +305,12 @@ impl NaiveDate { /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) /// (year and day of the year). /// - /// Returns `None` on the out-of-range date and/or invalid day of year. + /// # Errors + /// + /// Returns `None` if: + /// - The specified ordinal day does not exist (for example 2023-366). + /// - The value for `ordinal` is invalid (for example: `0`, `400`). + /// - `year` is out of range for `NaiveDate`. /// /// # Example /// @@ -356,7 +337,10 @@ impl NaiveDate { /// (year, week number and day of the week). /// The resulting `NaiveDate` may have a different year from the input year. /// - /// Panics on the out-of-range date and/or invalid week number. + /// # Panics + /// + /// Panics if the specified week does not exist in that year, on invalid values for `week`, or + /// if the resulting date is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] #[must_use] pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { @@ -367,7 +351,12 @@ impl NaiveDate { /// (year, week number and day of the week). /// The resulting `NaiveDate` may have a different year from the input year. /// - /// Returns `None` on the out-of-range date and/or invalid week number. + /// # Errors + /// + /// Returns `None` if: + /// - The specified week does not exist in that year (for example 2023 week 53). + /// - The value for `week` is invalid (for example: `0`, `60`). + /// - If the resulting date is out of range for `NaiveDate`. /// /// # Example /// @@ -443,6 +432,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with /// January 1, 1 being day 1. /// + /// # Panics + /// /// Panics if the date is out of range. #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] #[inline] @@ -454,6 +445,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with /// January 1, 1 being day 1. /// + /// # Errors + /// /// Returns `None` if the date is out of range. /// /// # Example @@ -481,15 +474,15 @@ impl NaiveDate { } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. /// + /// `n` is 1-indexed. + /// /// # Panics /// - /// The resulting `NaiveDate` is guaranteed to be in `month`. If `n` is larger than the number - /// of `weekday` in `month` (eg. the 6th Friday of March 2017) then this function will panic. - /// - /// `n` is 1-indexed. Passing `n=0` will cause a panic. + /// Panics if the specified day does not exist in that month, on invalid values for `month` or + /// `n`, or if `year` is out of range for `NaiveDate`. #[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")] #[must_use] pub fn from_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u8) -> NaiveDate { @@ -497,17 +490,25 @@ impl NaiveDate { } /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March - /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. `n` is 1-indexed. + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. + /// + /// `n` is 1-indexed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified day does not exist in that month (for example the 5th Monday of Apr. 2023). + /// - The value for `month` or `n` is invalid. + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example /// /// ``` /// use chrono::{NaiveDate, Weekday}; /// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), /// NaiveDate::from_ymd_opt(2017, 3, 10)) /// ``` - /// - /// Returns `None` if `n` out-of-range; ie. if `n` is larger than the number of `weekday` in - /// `month` (eg. the 6th Friday of March 2017), or if `n == 0`. #[must_use] pub fn from_weekday_of_month_opt( year: i32, @@ -596,10 +597,14 @@ impl NaiveDate { /// Add a duration in [`Months`] to the date /// - /// If the day would be out of range for the resulting month, use the last day for that month. + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// + /// # Example + /// /// ``` /// # use chrono::{NaiveDate, Months}; /// assert_eq!( @@ -625,10 +630,14 @@ impl NaiveDate { /// Subtract a duration in [`Months`] from the date /// - /// If the day would be out of range for the resulting month, use the last day for that month. + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors /// /// Returns `None` if the resulting date would be out of range. /// + /// # Example + /// /// ``` /// # use chrono::{NaiveDate, Months}; /// assert_eq!( @@ -699,8 +708,12 @@ impl NaiveDate { /// Add a duration in [`Days`] to the date /// + /// # Errors + /// /// Returns `None` if the resulting date would be out of range. /// + /// # Example + /// /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( @@ -727,8 +740,12 @@ impl NaiveDate { /// Subtract a duration in [`Days`] from the date /// + /// # Errors + /// /// Returns `None` if the resulting date would be out of range. /// + /// # Example + /// /// ``` /// # use chrono::{NaiveDate, Days}; /// assert_eq!( @@ -782,6 +799,8 @@ impl NaiveDate { /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. /// + /// # Panics + /// /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] #[inline] @@ -795,6 +814,8 @@ impl NaiveDate { /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. /// + /// # Errors + /// /// Returns `None` on invalid hour, minute and/or second. /// /// # Example @@ -819,6 +840,8 @@ impl NaiveDate { /// The millisecond part can exceed 1,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] #[inline] @@ -832,6 +855,8 @@ impl NaiveDate { /// The millisecond part can exceed 1,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or millisecond. /// /// # Example @@ -864,6 +889,8 @@ impl NaiveDate { /// The microsecond part can exceed 1,000,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or microsecond. /// /// # Example @@ -891,6 +918,8 @@ impl NaiveDate { /// The microsecond part can exceed 1,000,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or microsecond. /// /// # Example @@ -923,6 +952,8 @@ impl NaiveDate { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] #[inline] @@ -936,6 +967,8 @@ impl NaiveDate { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. /// /// # Example @@ -994,6 +1027,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the next calendar date. /// + /// # Panics + /// /// Panics when `self` is the last representable date. #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] #[inline] @@ -1004,6 +1039,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the next calendar date. /// + /// # Errors + /// /// Returns `None` when `self` is the last representable date. /// /// # Example @@ -1026,6 +1063,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the previous calendar date. /// + /// # Panics + /// /// Panics when `self` is the first representable date. #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] #[inline] @@ -1036,6 +1075,8 @@ impl NaiveDate { /// Makes a new `NaiveDate` for the previous calendar date. /// + /// # Errors + /// /// Returns `None` when `self` is the first representable date. /// /// # Example @@ -1056,9 +1097,11 @@ impl NaiveDate { } } - /// Adds the `days` part of given `Duration` to the current date. + /// Adds the number of whole days in the given `Duration` to the current date. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -1088,9 +1131,11 @@ impl NaiveDate { NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) } - /// Subtracts the `days` part of given `TimeDelta` from the current date. + /// Subtracts the number of whole days in the given `TimeDelta` from the current date. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -1156,6 +1201,10 @@ impl NaiveDate { } /// Returns the number of whole years from the given `base` until `self`. + /// + /// # Errors + /// + /// Returns `None` if `base < self`. #[must_use] pub fn years_since(&self, base: Self) -> Option { let mut years = self.year() - base.year(); @@ -1539,9 +1588,12 @@ impl Datelike for NaiveDate { isoweek::iso_week_from_yof(self.year(), self.of()) } - /// Makes a new `NaiveDate` with the year number changed. + /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or when the `NaiveDate` would be + /// out of range. /// /// # Example /// @@ -1575,7 +1627,9 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the month number (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid. /// /// # Example /// @@ -1594,7 +1648,10 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the month number (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `month0` is + /// invalid. /// /// # Example /// @@ -1614,7 +1671,9 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid. /// /// # Example /// @@ -1633,7 +1692,9 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid. /// /// # Example /// @@ -1653,7 +1714,10 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is + /// invalid. /// /// # Example /// @@ -1677,7 +1741,10 @@ impl Datelike for NaiveDate { /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is + /// invalid. /// /// # Example /// @@ -1898,13 +1965,10 @@ impl Iterator for NaiveDateDaysIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if self.value == NaiveDate::MAX { - return None; - } - // current < NaiveDate::MAX from here on: + // We return the current value, and have no way to return `NaiveDate::MAX`. let current = self.value; // This can't panic because current is < NaiveDate::MAX: - self.value = current.succ_opt().unwrap(); + self.value = current.succ_opt()?; Some(current) } @@ -1918,15 +1982,16 @@ impl ExactSizeIterator for NaiveDateDaysIterator {} impl DoubleEndedIterator for NaiveDateDaysIterator { fn next_back(&mut self) -> Option { - if self.value == NaiveDate::MIN { - return None; - } + // We return the current value, and have no way to return `NaiveDate::MIN`. let current = self.value; - self.value = current.pred_opt().unwrap(); + self.value = current.pred_opt()?; Some(current) } } +impl FusedIterator for NaiveDateDaysIterator {} + +/// Iterator over `NaiveDate` with a step size of one week. #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub struct NaiveDateWeeksIterator { value: NaiveDate, @@ -1936,11 +2001,8 @@ impl Iterator for NaiveDateWeeksIterator { type Item = NaiveDate; fn next(&mut self) -> Option { - if NaiveDate::MAX - self.value < TimeDelta::weeks(1) { - return None; - } let current = self.value; - self.value = current + TimeDelta::weeks(1); + self.value = current.checked_add_signed(TimeDelta::weeks(1))?; Some(current) } @@ -1954,18 +2016,13 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {} impl DoubleEndedIterator for NaiveDateWeeksIterator { fn next_back(&mut self) -> Option { - if self.value - NaiveDate::MIN < TimeDelta::weeks(1) { - return None; - } let current = self.value; - self.value = current - TimeDelta::weeks(1); + self.value = current.checked_sub_signed(TimeDelta::weeks(1))?; Some(current) } } -// TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator, -// TrustedLen, and Step once they becomes stable. -// See: https://github.com/chronotope/chrono/issues/208 +impl FusedIterator for NaiveDateWeeksIterator {} /// The `Debug` output of the naive date `d` is the same as /// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). @@ -2219,38 +2276,70 @@ mod serde { } } - #[test] - fn test_serde_serialize() { - super::test_encodable_json(serde_json::to_string); - } + #[cfg(test)] + mod tests { + use crate::naive::date::{test_decodable_json, test_encodable_json}; + use crate::NaiveDate; - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(input)); - } + #[test] + fn test_serde_serialize() { + test_encodable_json(serde_json::to_string); + } - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use bincode::{deserialize, serialize}; + #[test] + fn test_serde_deserialize() { + test_decodable_json(|input| serde_json::from_str(input)); + } - let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap(); - let encoded = serialize(&d).unwrap(); - let decoded: NaiveDate = deserialize(&encoded).unwrap(); - assert_eq!(d, decoded); + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap(); + let encoded = serialize(&d).unwrap(); + let decoded: NaiveDate = deserialize(&encoded).unwrap(); + assert_eq!(d, decoded); + } } } #[cfg(test)] mod tests { - use super::{ - Days, Months, NaiveDate, MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR, - }; + use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR}; use crate::time_delta::TimeDelta; use crate::{Datelike, Weekday}; use std::{i32, u32}; + // as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, + // we use a separate run-time test. + #[test] + fn test_date_bounds() { + let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap(); + let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); + assert!( + NaiveDate::MIN == calculated_min, + "`NaiveDate::MIN` should have a year flag {:?}", + calculated_min.of().flags() + ); + assert!( + NaiveDate::MAX == calculated_max, + "`NaiveDate::MAX` should have a year flag {:?}", + calculated_max.of().flags() + ); + + // let's also check that the entire range do not exceed 2^44 seconds + // (sometimes used for bounding `Duration` against overflow) + let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); + let maxsecs = maxsecs + 86401; // also take care of DateTime + assert!( + maxsecs < (1 << MAX_BITS), + "The entire `NaiveDate` range somehow exceeds 2^{} seconds", + MAX_BITS + ); + } + #[test] fn diff_months() { // identity @@ -3102,4 +3191,25 @@ mod tests { assert!(dt.with_day0(4294967295).is_none()); assert!(dt.with_ordinal0(4294967295).is_none()); } + + // MAX_YEAR-12-31 minus 0000-01-01 + // = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day + // = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days + // = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days + const MAX_DAYS_FROM_YEAR_0: i32 = + MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365; + + // MIN_YEAR-01-01 minus 0000-01-01 + // = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01 + // = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01) + // = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days + // + // n is set to 1000 for convenience. + const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4 + - (MIN_YEAR + 400_000) / 100 + + (MIN_YEAR + 400_000) / 400 + - 146_097_000; + + // only used for testing, but duplicated in naive::datetime + const MAX_BITS: usize = 44; } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 818f72e4..d10ea89d 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -109,7 +109,11 @@ impl NaiveDateTime { /// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX /// timestamp" cannot represent a leap second unambiguously.) /// - /// Panics on the out-of-range number of seconds and/or invalid nanosecond. + /// # Panics + /// + /// Panics if the number of seconds would be out of range for a `NaiveDateTime` (more than + /// ca. 262,000 years away from common era), and panics on an invalid nanosecond (2 seconds or + /// more). #[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")] #[inline] #[must_use] @@ -122,7 +126,10 @@ impl NaiveDateTime { /// /// The UNIX epoch starts on midnight, January 1, 1970, UTC. /// - /// Returns `None` on an out-of-range number of milliseconds. + /// # Errors + /// + /// Returns `None` if the number of milliseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) /// /// # Example /// @@ -151,7 +158,10 @@ impl NaiveDateTime { /// /// The UNIX epoch starts on midnight, January 1, 1970, UTC. /// - /// Returns `None` on an out-of-range number of microseconds. + /// # Errors + /// + /// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) /// /// # Example /// @@ -185,8 +195,11 @@ impl NaiveDateTime { /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) /// - /// Returns `None` on the out-of-range number of seconds (more than 262 000 years away - /// from common era) and/or invalid nanosecond (2 seconds or more). + /// # Errors + /// + /// Returns `None` if the number of seconds would be out of range for a `NaiveDateTime` (more + /// than ca. 262,000 years away from common era), and panics on an invalid nanosecond + /// (2 seconds or more). /// /// # Example /// @@ -386,11 +399,6 @@ impl NaiveDateTime { /// Note that this does *not* account for the timezone! /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 Million. (If this is a problem, - /// please file an issue to let me know what domain needs millisecond - /// precision over billions of years, I'm curious.) - /// /// # Example /// /// ``` @@ -417,11 +425,6 @@ impl NaiveDateTime { /// Note that this does *not* account for the timezone! /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 Thousand. (If this is a problem, - /// please file an issue to let me know what domain needs microsecond - /// precision over millennia, I'm curious.) - /// /// # Example /// /// ``` @@ -447,13 +450,11 @@ impl NaiveDateTime { /// /// # Panics /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 years. The dates that can be - /// represented as nanoseconds are between 1677-09-21T00:12:44.0 and - /// 2262-04-11T23:47:16.854775804. + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `NaiveDateTime`. /// - /// (If this is a problem, please file an issue to let me know what domain - /// needs nanosecond precision over millennia, I'm curious.) + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. /// /// # Example /// @@ -476,8 +477,10 @@ impl NaiveDateTime { #[inline] #[must_use] pub fn timestamp_nanos(&self) -> i64 { - let as_ns = self.timestamp() * 1_000_000_000; - as_ns + i64::from(self.timestamp_subsec_nanos()) + self.timestamp() + .checked_mul(1_000_000_000) + .and_then(|ns| ns.checked_add(i64::from(self.timestamp_subsec_nanos()))) + .expect("value can not be represented in a timestamp with nanosecond precision.") } /// Returns the number of milliseconds since the last whole non-leap second. @@ -553,7 +556,9 @@ impl NaiveDateTime { /// except when the `NaiveDateTime` itself represents a leap second /// in which case the assumption becomes that **there is exactly a single leap second ever**. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -626,9 +631,11 @@ impl NaiveDateTime { /// Adds given `Months` to the current date and time. /// - /// Returns `None` when it will result in overflow. + /// Uses the last day of the month if the day does not exist in the resulting month. /// - /// Overflow returns `None`. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -659,7 +666,9 @@ impl NaiveDateTime { /// except when the `NaiveDateTime` itself represents a leap second /// in which case the assumption becomes that **there is exactly a single leap second ever**. /// - /// Returns `None` when it will result in overflow. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -728,9 +737,11 @@ impl NaiveDateTime { /// Subtracts given `Months` from the current date and time. /// - /// Returns `None` when it will result in overflow. + /// Uses the last day of the month if the day does not exist in the resulting month. /// - /// Overflow returns `None`. + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. /// /// # Example /// @@ -1091,12 +1102,16 @@ impl Datelike for NaiveDateTime { self.date.iso_week() } - /// Makes a new `NaiveDateTime` with the year number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// Makes a new `NaiveDateTime` with the year number changed, while keeping the same month and + /// day. /// /// See also the [`NaiveDate::with_year`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or when the `NaiveDateTime` would be + /// out of range. + /// /// # Example /// /// ``` @@ -1113,10 +1128,12 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_month`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid. + /// /// # Example /// /// ``` @@ -1134,10 +1151,13 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_month0`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `month0` is + /// invalid. + /// /// # Example /// /// ``` @@ -1155,10 +1175,12 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_day`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid. + /// /// # Example /// /// ``` @@ -1175,10 +1197,12 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_day0`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid. + /// /// # Example /// /// ``` @@ -1195,10 +1219,13 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_ordinal`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is + /// invalid. + /// /// # Example /// /// ``` @@ -1222,10 +1249,13 @@ impl Datelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveDate::with_ordinal0`] method. /// + /// # Errors + /// + /// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is + /// invalid. + /// /// # Example /// /// ``` @@ -1321,10 +1351,12 @@ impl Timelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the hour number changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// /// See also the [`NaiveTime::with_hour`] method. /// + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. + /// /// # Example /// /// ``` @@ -1342,10 +1374,11 @@ impl Timelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the minute number changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// See also the [`NaiveTime::with_minute`] method. /// - /// See also the - /// [`NaiveTime::with_minute`] method. + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. /// /// # Example /// @@ -1364,12 +1397,15 @@ impl Timelike for NaiveDateTime { /// Makes a new `NaiveDateTime` with the second number changed. /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. As - /// with the [`NaiveDateTime::second`] method, the input range is - /// restricted to 0 through 59. + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. /// /// See also the [`NaiveTime::with_second`] method. /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// /// # Example /// /// ``` @@ -1393,6 +1429,10 @@ impl Timelike for NaiveDateTime { /// /// See also the [`NaiveTime::with_nanosecond`] method. /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// /// # Example /// /// ``` @@ -1417,7 +1457,9 @@ impl Timelike for NaiveDateTime { /// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case /// the assumption becomes that **there is exactly a single leap second ever**. /// -/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_add_signed`] +/// # Panics +/// +/// Panics if the resulting date would be out of range. Use [`NaiveDateTime::checked_add_signed`] /// to detect that. /// /// # Example diff --git a/src/naive/datetime/serde.rs b/src/naive/datetime/serde.rs index 8107f384..1a97fb23 100644 --- a/src/naive/datetime/serde.rs +++ b/src/naive/datetime/serde.rs @@ -110,7 +110,6 @@ pub mod ts_nanoseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where S: ser::Serializer, @@ -138,7 +137,6 @@ pub mod ts_nanoseconds { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -233,7 +231,6 @@ pub mod ts_nanoseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, @@ -264,7 +261,6 @@ pub mod ts_nanoseconds_option { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -362,7 +358,6 @@ pub mod ts_microseconds { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where S: ser::Serializer, @@ -390,7 +385,6 @@ pub mod ts_microseconds { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -488,7 +482,6 @@ pub mod ts_microseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, @@ -519,7 +512,6 @@ pub mod ts_microseconds_option { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -617,7 +609,6 @@ pub mod ts_milliseconds { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where S: ser::Serializer, @@ -645,7 +636,6 @@ pub mod ts_milliseconds { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -740,7 +730,6 @@ pub mod ts_milliseconds_option { /// assert_eq!(as_string, r#"{"time":1526522699918}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, @@ -771,7 +760,6 @@ pub mod ts_milliseconds_option { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -869,7 +857,6 @@ pub mod ts_seconds { /// assert_eq!(as_string, r#"{"time":1431684000}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result where S: ser::Serializer, @@ -897,7 +884,6 @@ pub mod ts_seconds { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0).unwrap() }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result where D: de::Deserializer<'de>, @@ -989,7 +975,6 @@ pub mod ts_seconds_option { /// assert_eq!(as_string, r#"{"time":1526522699}"#); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn serialize(opt: &Option, serializer: S) -> Result where S: ser::Serializer, @@ -1020,7 +1005,6 @@ pub mod ts_seconds_option { /// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0) }); /// # Ok::<(), serde_json::Error>(()) /// ``` - #[must_use] pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de>, @@ -1063,51 +1047,6 @@ pub mod ts_seconds_option { } } -#[test] -fn test_serde_serialize() { - super::test_encodable_json(serde_json::to_string); -} - -#[test] -fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(input)); -} - -// Bincode is relevant to test separately from JSON because -// it is not self-describing. -#[test] -fn test_serde_bincode() { - use crate::NaiveDate; - use bincode::{deserialize, serialize}; - - let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap(); - let encoded = serialize(&dt).unwrap(); - let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); -} - -#[test] -fn test_serde_bincode_optional() { - use crate::prelude::*; - use crate::serde::ts_nanoseconds_option; - use bincode::{deserialize, serialize}; - use serde_derive::{Deserialize, Serialize}; - - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Test { - one: Option, - #[serde(with = "ts_nanoseconds_option")] - two: Option>, - } - - let expected = - Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) }; - let bytes: Vec = serialize(&expected).unwrap(); - let actual = deserialize::(&(bytes)).unwrap(); - - assert_eq!(expected, actual); -} - // lik? function to convert a LocalResult into a serde-ish Result pub(crate) fn serde_from(me: LocalResult, ts: &V) -> Result where @@ -1155,3 +1094,51 @@ impl fmt::Display for SerdeError { } } } + +#[cfg(test)] +mod tests { + use crate::naive::datetime::{test_decodable_json, test_encodable_json}; + use crate::serde::ts_nanoseconds_option; + use crate::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc}; + + use bincode::{deserialize, serialize}; + use serde_derive::{Deserialize, Serialize}; + + #[test] + fn test_serde_serialize() { + test_encodable_json(serde_json::to_string); + } + + #[test] + fn test_serde_deserialize() { + test_decodable_json(|input| serde_json::from_str(input)); + } + + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + #[test] + fn test_serde_bincode() { + let dt = + NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + } + + #[test] + fn test_serde_bincode_optional() { + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test { + one: Option, + #[serde(with = "ts_nanoseconds_option")] + two: Option>, + } + + let expected = + Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) }; + let bytes: Vec = serialize(&expected).unwrap(); + let actual = deserialize::(&(bytes)).unwrap(); + + assert_eq!(expected, actual); + } +} diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 35ac30ae..5d9f8e9f 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -369,6 +369,24 @@ fn test_nanosecond_range() { ); } +#[test] +#[should_panic] +fn test_nanosecond_just_beyond_range() { + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::milliseconds(300); + let _ = beyond_max.timestamp_nanos(); +} + +#[test] +#[should_panic] +fn test_nanosecond_far_beyond_range() { + let maximum = "2262-04-11T23:47:16.854775804"; + let parsed: NaiveDateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::days(365); + let _ = beyond_max.timestamp_nanos(); +} + #[test] fn test_and_local_timezone() { let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f1bd6d6c..17d3073b 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -10,8 +10,9 @@ mod internals; mod isoweek; mod time; +pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator, NaiveWeek}; #[allow(deprecated)] -pub use self::date::{Days, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; +pub use self::date::{MAX_DATE, MIN_DATE}; #[allow(deprecated)] pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; pub use self::isoweek::IsoWeek; diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 356f2121..856444f6 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -208,6 +208,8 @@ impl NaiveTime { /// No [leap second](#leap-second-handling) is allowed here; /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. /// + /// # Panics + /// /// Panics on invalid hour, minute and/or second. #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] #[inline] @@ -221,6 +223,8 @@ impl NaiveTime { /// No [leap second](#leap-second-handling) is allowed here; /// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead. /// + /// # Errors + /// /// Returns `None` on invalid hour, minute and/or second. /// /// # Example @@ -247,6 +251,8 @@ impl NaiveTime { /// The millisecond part can exceed 1,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or millisecond. #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] #[inline] @@ -260,6 +266,8 @@ impl NaiveTime { /// The millisecond part can exceed 1,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or millisecond. /// /// # Example @@ -290,6 +298,8 @@ impl NaiveTime { /// The microsecond part can exceed 1,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or microsecond. #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] #[inline] @@ -303,6 +313,8 @@ impl NaiveTime { /// The microsecond part can exceed 1,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or microsecond. /// /// # Example @@ -331,6 +343,8 @@ impl NaiveTime { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid hour, minute, second and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] #[inline] @@ -344,6 +358,8 @@ impl NaiveTime { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. /// /// # Example @@ -376,6 +392,8 @@ impl NaiveTime { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Panics + /// /// Panics on invalid number of seconds and/or nanosecond. #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] #[inline] @@ -389,6 +407,8 @@ impl NaiveTime { /// The nanosecond part can exceed 1,000,000,000 /// in order to represent the [leap second](#leap-second-handling). /// + /// # Errors + /// /// Returns `None` on invalid number of seconds and/or nanosecond. /// /// # Example @@ -502,10 +522,8 @@ impl NaiveTime { parsed.to_naive_time().map(|t| (t, remainder)) } - /// Adds given `TimeDelta` to the current time, - /// and also returns the number of *seconds* + /// Adds given `TimeDelta` to the current time, and also returns the number of *seconds* /// in the integral number of days ignored from the addition. - /// (We cannot return `TimeDelta` because it is subject to overflow or underflow.) /// /// # Example /// @@ -585,10 +603,8 @@ impl NaiveTime { (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) } - /// Subtracts given `TimeDelta` from the current time, - /// and also returns the number of *seconds* + /// Subtracts given `TimeDelta` from the current time, and also returns the number of *seconds* /// in the integral number of days ignored from the subtraction. - /// (We cannot return `TimeDelta` because it is subject to overflow or underflow.) /// /// # Example /// @@ -882,7 +898,9 @@ impl Timelike for NaiveTime { /// Makes a new `NaiveTime` with the hour number changed. /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. /// /// # Example /// @@ -904,7 +922,9 @@ impl Timelike for NaiveTime { /// Makes a new `NaiveTime` with the minute number changed. /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. /// /// # Example /// @@ -926,10 +946,13 @@ impl Timelike for NaiveTime { /// Makes a new `NaiveTime` with the second number changed. /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. /// As with the [`second`](#method.second) method, /// the input range is restricted to 0 through 59. /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// /// # Example /// /// ``` @@ -950,10 +973,13 @@ impl Timelike for NaiveTime { /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. /// As with the [`nanosecond`](#method.nanosecond) method, /// the input range can exceed 1,000,000,000 for leap seconds. /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// /// # Example /// /// ``` diff --git a/src/naive/time/serde.rs b/src/naive/time/serde.rs index c7394fb5..cf3c4e3d 100644 --- a/src/naive/time/serde.rs +++ b/src/naive/time/serde.rs @@ -42,24 +42,30 @@ impl<'de> de::Deserialize<'de> for NaiveTime { } } -#[test] -fn test_serde_serialize() { - super::test_encodable_json(serde_json::to_string); -} +#[cfg(test)] +mod tests { + use crate::naive::time::{test_decodable_json, test_encodable_json}; + use crate::NaiveTime; -#[test] -fn test_serde_deserialize() { - super::test_decodable_json(|input| serde_json::from_str(input)); -} + #[test] + fn test_serde_serialize() { + test_encodable_json(serde_json::to_string); + } -#[test] -fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use bincode::{deserialize, serialize}; + #[test] + fn test_serde_deserialize() { + test_decodable_json(|input| serde_json::from_str(input)); + } - let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); - let encoded = serialize(&t).unwrap(); - let decoded: NaiveTime = deserialize(&encoded).unwrap(); - assert_eq!(t, decoded); + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + let encoded = serialize(&t).unwrap(); + let decoded: NaiveTime = deserialize(&encoded).unwrap(); + assert_eq!(t, decoded); + } } diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 583a17b1..c7a938dd 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -50,15 +50,34 @@ mod inner { not(any(target_os = "emscripten", target_os = "wasi")) ))] mod inner { - use crate::{FixedOffset, LocalResult, NaiveDateTime}; + use crate::{Datelike, FixedOffset, LocalResult, NaiveDateTime, Timelike}; - pub(super) fn offset_from_utc_datetime(_utc: &NaiveDateTime) -> LocalResult { - let offset = js_sys::Date::new_0().get_timezone_offset(); + pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset(); LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { - offset_from_utc_datetime(local) + let mut year = local.year(); + if year < 100 { + // The API in `js_sys` does not let us create a `Date` with negative years. + // And values for years from `0` to `99` map to the years `1900` to `1999`. + // Shift the value by a multiple of 400 years until it is `>= 100`. + let shift_cycles = (year - 100).div_euclid(400); + year -= shift_cycles * 400; + } + let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec( + year as u32, + local.month0() as i32, + local.day() as i32, + local.hour() as i32, + local.minute() as i32, + local.second() as i32, + // ignore milliseconds, our representation of leap seconds may be problematic + ); + let offset = js_date.get_timezone_offset(); + // We always get a result, even if this time does not exist or is ambiguous. + LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } } @@ -95,33 +114,9 @@ impl Local { } /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - )))] - #[must_use] pub fn now() -> DateTime { Utc::now().with_timezone(&Local) } - - /// Returns a `DateTime` which corresponds to the current date and time. - #[cfg(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) - ))] - #[must_use] - pub fn now() -> DateTime { - use super::Utc; - let now: DateTime = super::Utc::now(); - - // Workaround missing timezone logic in `time` crate - let offset = - FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) - .unwrap(); - DateTime::from_utc(now.naive_utc(), offset) - } } impl TimeZone for Local { diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 33c89060..d2de0602 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -321,7 +321,7 @@ impl<'a> TimeZoneRef<'a> { // Check leap seconds if !(self.leap_seconds.is_empty() || self.leap_seconds[0].unix_leap_time >= 0 - && saturating_abs(self.leap_seconds[0].correction) == 1) + && self.leap_seconds[0].correction.saturating_abs() == 1) { return Err(Error::TimeZone("invalid leap second")); } @@ -336,7 +336,7 @@ impl<'a> TimeZoneRef<'a> { let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time); let abs_diff_correction = - saturating_abs(x1.correction.saturating_sub(x0.correction)); + x1.correction.saturating_sub(x0.correction).saturating_abs(); if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) { return Err(Error::TimeZone("invalid leap second")); @@ -615,17 +615,6 @@ fn find_tz_file(path: impl AsRef) -> Result { } } -#[inline] -const fn saturating_abs(v: i32) -> i32 { - if v.is_positive() { - v - } else if v == i32::min_value() { - i32::max_value() - } else { - -v - } -} - // Possible system timezone directories #[cfg(unix)] const ZONE_INFO_DIRECTORIES: [&str; 4] = diff --git a/src/traits.rs b/src/traits.rs index f92c8526..f672d27f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -53,7 +53,7 @@ pub trait Datelike: Sized { /// Returns the ISO week. fn iso_week(&self) -> IsoWeek; - /// Makes a new value with the year number changed. + /// Makes a new value with the year number changed, while keeping the same month and day. /// /// Returns `None` when the resulting value would be invalid. fn with_year(&self, year: i32) -> Option; diff --git a/src/weekday.rs b/src/weekday.rs index a67ff5c2..eec57afb 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -195,43 +195,6 @@ impl fmt::Debug for ParseWeekdayError { } } -#[cfg(test)] -mod tests { - use super::Weekday; - - #[test] - fn test_num_days_from() { - for i in 0..7 { - let base_day = Weekday::try_from(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")] @@ -275,8 +238,46 @@ mod weekday_serde { deserializer.deserialize_str(WeekdayVisitor) } } +} + +#[cfg(test)] +mod tests { + use super::Weekday; #[test] + fn test_num_days_from() { + for i in 0..7 { + let base_day = Weekday::try_from(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 + ); + } + } + + #[test] + #[cfg(feature = "serde")] fn test_serde_serialize() { use serde_json::to_string; use Weekday::*; @@ -298,6 +299,7 @@ mod weekday_serde { } #[test] + #[cfg(feature = "serde")] fn test_serde_deserialize() { use serde_json::from_str; use Weekday::*; diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 00000000..5fe42eb8 --- /dev/null +++ b/taplo.toml @@ -0,0 +1,4 @@ +include = ["deny.toml", "**/Cargo.toml"] + +[formatting] +inline_table_expand = false diff --git a/tests/dateutils.rs b/tests/dateutils.rs index c11d5f98..21ed1f1e 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -58,12 +58,13 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { /// for testing only #[allow(dead_code)] #[cfg(not(target_os = "aix"))] -const DATE_PATH: &'static str = "/usr/bin/date"; +const DATE_PATH: &str = "/usr/bin/date"; #[allow(dead_code)] #[cfg(target_os = "aix")] -const DATE_PATH: &'static str = "/opt/freeware/bin/date"; +const DATE_PATH: &str = "/opt/freeware/bin/date"; #[cfg(test)] +#[cfg(unix)] /// test helper to sanity check the date command behaves as expected /// asserts the command succeeded fn assert_run_date_version() { diff --git a/tests/wasm.rs b/tests/wasm.rs index f003d4db..6863502b 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,11 +1,18 @@ +//! Run this test with: +//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind` +//! +//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with +//! the host system. +//! The check will fail if the local timezone does not match one of the timezones defined below. + #![cfg(all( target_arch = "wasm32", feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] -use self::chrono::prelude::*; -use self::wasm_bindgen_test::*; +use chrono::prelude::*; +use wasm_bindgen_test::*; #[wasm_bindgen_test] fn now() { @@ -17,7 +24,7 @@ fn now() { let actual = Utc.datetime_from_str(&now, "%s").unwrap(); let diff = utc - actual; assert!( - diff < chrono::Duration::minutes(5), + diff < chrono::TimeDelta::minutes(5), "expected {} - {} == {} < 5m (env var: {})", utc, actual, @@ -52,7 +59,7 @@ fn from_is_exact() { let dt = DateTime::::from(now.clone()); - assert_eq!(now.get_time() as i64, dt.timestamp_millis_opt().unwrap()); + assert_eq!(now.get_time() as i64, dt.timestamp_millis()); } #[wasm_bindgen_test] @@ -72,7 +79,7 @@ fn convert_all_parts_with_milliseconds() { let js_date = js_sys::Date::from(time); assert_eq!(js_date.get_utc_full_year(), 2020); - assert_eq!(js_date.get_utc_month(), 12); + assert_eq!(js_date.get_utc_month(), 11); // months are numbered 0..=11 assert_eq!(js_date.get_utc_date(), 1); assert_eq!(js_date.get_utc_hours(), 3); assert_eq!(js_date.get_utc_minutes(), 1);